فهرست منبع

[unity] Major render refactoring. Add clipping. See SpineMesh.cs

pharan 8 سال پیش
والد
کامیت
e185e7f310

+ 4 - 0
spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs

@@ -919,6 +919,10 @@ namespace Spine.Unity.Editor {
 			public PointAttachment NewPointAttachment (Skin skin, string name) {
 				return new PointAttachment(name);
 			}
+
+			public ClippingAttachment NewClippingAttachment (Skin skin, string name) {
+				return new ClippingAttachment(name);
+			}
 		}
 		#endregion
 

+ 0 - 488
spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysMeshGenerator.cs

@@ -1,488 +0,0 @@
-/******************************************************************************
- * 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.
- *****************************************************************************/
-
-#define SPINE_OPTIONAL_NORMALS
-using UnityEngine;
-
-namespace Spine.Unity.MeshGeneration {
-	public class ArraysMeshGenerator {
-		#region Settings
-		public bool PremultiplyVertexColors { get; set; }
-		protected bool addNormals;
-		public bool AddNormals { get { return addNormals; } set { addNormals = value; } }
-		protected bool addTangents;
-		public bool AddTangents { get { return addTangents; } set { addTangents = value; } }
-		protected bool addBlackTint;
-		public bool AddBlackTint { get { return addBlackTint; } set { addBlackTint = value; } }
-		#endregion
-
-		protected float[] attachmentVertexBuffer = new float[8];
-		protected Vector3[] meshVertices;
-		protected Color32[] meshColors32;
-		protected Vector2[] meshUVs;
-
-		#if SPINE_OPTIONAL_NORMALS
-		protected Vector3[] meshNormals;
-		#endif
-		protected Vector4[] meshTangents;
-		protected Vector2[] tempTanBuffer;
-
-		protected Vector2[] uv2, uv3; // Black tint
-
-		public void TryAddNormalsTo (Mesh mesh, int targetVertexCount) {
-			#if SPINE_OPTIONAL_NORMALS
-			if (addNormals) {
-				bool verticesWasResized = this.meshNormals == null || meshNormals.Length < targetVertexCount;
-				if (verticesWasResized) {
-					this.meshNormals = new Vector3[targetVertexCount];
-					Vector3 fixedNormal = new Vector3(0, 0, -1f);
-					Vector3[] normals = this.meshNormals;
-					for (int i = 0; i < targetVertexCount; i++)
-						normals[i] = fixedNormal;
-				}
-
-				mesh.normals = this.meshNormals;
-			}
-			#endif
-		}
-
-		/// <summary>Ensures the sizes of the passed array references. If they are not the correct size, a new array will be assigned to the references.</summary>
-		/// <returns><c>true</c>, if a resize occurred, <c>false</c> otherwise.</returns>
-		public static bool EnsureSize (int targetVertexCount, ref Vector3[] vertices, ref Vector2[] uvs, ref Color32[] colors) {
-			Vector3[] verts = vertices;
-			bool verticesWasResized = verts == null || targetVertexCount > verts.Length;
-			if (verticesWasResized) {
-				// Not enough space, increase size.
-				vertices = new Vector3[targetVertexCount];
-				colors = new Color32[targetVertexCount];
-				uvs = new Vector2[targetVertexCount];
-			} else {
-				// Too many vertices, zero the extra.
-				Vector3 zero = Vector3.zero;
-				for (int i = targetVertexCount, n = verts.Length; i < n; i++)
-					verts[i] = zero;
-			}
-			return verticesWasResized;
-		}
-
-		public static bool EnsureSize (int targetVertexCount, ref Vector2[] buffer) {
-			Vector2[] buff = buffer;
-			bool verticesWasResized = (buffer == null || targetVertexCount > buffer.Length);
-			if (verticesWasResized) {
-				buffer = new Vector2[targetVertexCount];
-			} else {
-				Vector3 zero = Vector3.zero;
-				for (int i = targetVertexCount, n = buff.Length; i < n; i++)
-					buff[i] = zero;
-			}
-			return verticesWasResized;
-		}
-
-		public static bool EnsureTriangleBuffersSize (ExposedList<SubmeshTriangleBuffer> submeshBuffers, int targetSubmeshCount, SubmeshInstruction[] instructionItems) {
-			bool submeshBuffersWasResized = submeshBuffers.Count < targetSubmeshCount;
-			if (submeshBuffersWasResized) {
-				submeshBuffers.GrowIfNeeded(targetSubmeshCount - submeshBuffers.Count);
-				for (int i = submeshBuffers.Count; submeshBuffers.Count < targetSubmeshCount; i++)
-					submeshBuffers.Add(new SubmeshTriangleBuffer(instructionItems[i].triangleCount));
-			}
-			return submeshBuffersWasResized;
-		}
-
-		public static void FillBlackUVs (Skeleton skeleton, int startSlot, int endSlot, Vector2[] uv2, Vector2[] uv3, int vertexIndex, bool renderMeshes = true) {
-			var skeletonDrawOrderItems = skeleton.DrawOrder.Items;
-			Vector2 rg, b2;
-			int vi = vertexIndex;
-			b2.y = 1f;
-
-			// drawOrder[endSlot] is excluded
-			for (int slotIndex = startSlot; slotIndex < endSlot; slotIndex++) {
-				var slot = skeletonDrawOrderItems[slotIndex];
-				var attachment = slot.attachment;
-
-				rg.x = slot.r2; //r
-				rg.y = slot.g2; //g
-				b2.x = slot.b2; //b
-
-				var regionAttachment = attachment as RegionAttachment;
-				if (regionAttachment != null) {
-					uv2[vi] = rg; uv2[vi + 1] = rg; uv2[vi + 2] = rg; uv2[vi + 3] = rg;
-					uv3[vi] = b2; uv3[vi + 1] = b2; uv3[vi + 2] = b2; uv3[vi + 3] = b2;
-					vi += 4;
-				} else if (renderMeshes) {
-					var meshAttachment = attachment as MeshAttachment;
-					if (meshAttachment != null) {
-						int meshVertexCount = meshAttachment.worldVerticesLength;
-						for (int iii = 0; iii < meshVertexCount; iii += 2) {
-							uv2[vi] = rg;
-							uv3[vi] = b2;
-							vi++;
-						}
-					}
-				}
-			}
-		}
-
-		/// <summary>Fills Unity vertex data buffers with verts from the Spine Skeleton.</summary>
-		/// <param name="skeleton">Spine.Skeleton source of the drawOrder array</param>
-		/// <param name="startSlot">Slot index of the first slot.</param>
-		/// <param name="endSlot">The index bounding the slot list. [endSlot - 1] is the last slot to be added.</param>
-		/// <param name="zSpacing">Spacing along the z-axis between attachments.</param>
-		/// <param name="pmaColors">If set to <c>true</c>, vertex colors will be premultiplied. This will also enable additive.</param>
-		/// <param name="verts">Vertex positions array. </param>
-		/// <param name="uvs">Vertex UV array.</param>
-		/// <param name="colors">Vertex color array (Color32).</param>
-		/// <param name="vertexIndex">A reference to the running vertex index. This is used when more than one submesh is to be added.</param>
-		/// <param name="tempVertBuffer">A temporary vertex position buffer for attachment position values.</param>
-		/// <param name="boundsMin">Reference to the running calculated minimum bounds.</param>
-		/// <param name="boundsMax">Reference to the running calculated maximum bounds.</param>
-		/// <param name = "renderMeshes">Include MeshAttachments. If false, it will ignore MeshAttachments.</param>
-		public static void FillVerts (Skeleton skeleton, int startSlot, int endSlot, float zSpacing, bool pmaColors, Vector3[] verts, Vector2[] uvs, Color32[] colors, ref int vertexIndex, ref float[] tempVertBuffer, ref Vector3 boundsMin, ref Vector3 boundsMax, bool renderMeshes = true) {
-			Color32 color;
-			var skeletonDrawOrderItems = skeleton.DrawOrder.Items;
-			float a = skeleton.a * 255, r = skeleton.r, g = skeleton.g, b = skeleton.b;
-
-			int vi = vertexIndex;
-			var tempVerts = tempVertBuffer;
-			Vector3 bmin = boundsMin;
-			Vector3 bmax = boundsMax;
-
-			// drawOrder[endSlot] is excluded
-			for (int slotIndex = startSlot; slotIndex < endSlot; slotIndex++) {
-				var slot = skeletonDrawOrderItems[slotIndex];
-				var attachment = slot.attachment;
-				float z = slotIndex * zSpacing;
-
-				var regionAttachment = attachment as RegionAttachment;
-				if (regionAttachment != null) {
-					regionAttachment.ComputeWorldVertices(slot.bone, tempVerts, 0);
-
-					float x1 = tempVerts[RegionAttachment.BLX], y1 = tempVerts[RegionAttachment.BLY];
-					float x2 = tempVerts[RegionAttachment.ULX], y2 = tempVerts[RegionAttachment.ULY];
-					float x3 = tempVerts[RegionAttachment.URX], y3 = tempVerts[RegionAttachment.URY];
-					float x4 = tempVerts[RegionAttachment.BRX], y4 = tempVerts[RegionAttachment.BRY];
-					verts[vi].x = x1; verts[vi].y = y1; verts[vi].z = z;
-					verts[vi + 1].x = x4; verts[vi + 1].y = y4; verts[vi + 1].z = z;
-					verts[vi + 2].x = x2; verts[vi + 2].y = y2; verts[vi + 2].z = z;
-					verts[vi + 3].x = x3; verts[vi + 3].y = y3;	verts[vi + 3].z = z;
-
-					if (pmaColors) {
-						color.a = (byte)(a * slot.a * regionAttachment.a);
-						color.r = (byte)(r * slot.r * regionAttachment.r * color.a);
-						color.g = (byte)(g * slot.g * regionAttachment.g * color.a);
-						color.b = (byte)(b * slot.b * regionAttachment.b * color.a);
-						if (slot.data.blendMode == BlendMode.Additive) color.a = 0;
-					} else {
-						color.a = (byte)(a * slot.a * regionAttachment.a);
-						color.r = (byte)(r * slot.r * regionAttachment.r * 255);
-						color.g = (byte)(g * slot.g * regionAttachment.g * 255);
-						color.b = (byte)(b * slot.b * regionAttachment.b * 255);
-					}
-
-					colors[vi] = color; colors[vi + 1] = color; colors[vi + 2] = color; colors[vi + 3] = color;
-
-					float[] regionUVs = regionAttachment.uvs;
-					uvs[vi].x = regionUVs[RegionAttachment.BLX]; uvs[vi].y = regionUVs[RegionAttachment.BLY];
-					uvs[vi + 1].x = regionUVs[RegionAttachment.BRX]; uvs[vi + 1].y = regionUVs[RegionAttachment.BRY];
-					uvs[vi + 2].x = regionUVs[RegionAttachment.ULX]; uvs[vi + 2].y = regionUVs[RegionAttachment.ULY];
-					uvs[vi + 3].x = regionUVs[RegionAttachment.URX]; uvs[vi + 3].y = regionUVs[RegionAttachment.URY];
-
-					if (x1 < bmin.x) bmin.x = x1; // Potential first attachment bounds initialization. Initial min should not block initial max. Same for Y below.
-					if (x1 > bmax.x) bmax.x = x1;
-					if (x2 < bmin.x) bmin.x = x2;
-					else if (x2 > bmax.x) bmax.x = x2;
-					if (x3 < bmin.x) bmin.x = x3;
-					else if (x3 > bmax.x) bmax.x = x3;
-					if (x4 < bmin.x) bmin.x = x4;
-					else if (x4 > bmax.x) bmax.x = x4;
-
-					if (y1 < bmin.y) bmin.y = y1;
-					if (y1 > bmax.y) bmax.y = y1;
-					if (y2 < bmin.y) bmin.y = y2;
-					else if (y2 > bmax.y) bmax.y = y2;
-					if (y3 < bmin.y) bmin.y = y3;
-					else if (y3 > bmax.y) bmax.y = y3;
-					if (y4 < bmin.y) bmin.y = y4;
-					else if (y4 > bmax.y) bmax.y = y4;
-
-					vi += 4;
-				} else if (renderMeshes) {
-					var meshAttachment = attachment as MeshAttachment;
-					if (meshAttachment != null) {
-						int meshVertexCount = meshAttachment.worldVerticesLength;
-						if (tempVerts.Length < meshVertexCount) tempVerts = new float[meshVertexCount];
-						meshAttachment.ComputeWorldVertices(slot, tempVerts);
-
-						if (pmaColors) {
-							color.a = (byte)(a * slot.a * meshAttachment.a);
-							color.r = (byte)(r * slot.r * meshAttachment.r * color.a);
-							color.g = (byte)(g * slot.g * meshAttachment.g * color.a);
-							color.b = (byte)(b * slot.b * meshAttachment.b * color.a);
-							if (slot.data.blendMode == BlendMode.Additive) color.a = 0;
-						} else {
-							color.a = (byte)(a * slot.a * meshAttachment.a);
-							color.r = (byte)(r * slot.r * meshAttachment.r * 255);
-							color.g = (byte)(g * slot.g * meshAttachment.g * 255);
-							color.b = (byte)(b * slot.b * meshAttachment.b * 255);
-						}
-
-						float[] attachmentUVs = meshAttachment.uvs;
-
-						// Potential first attachment bounds initialization. See conditions in RegionAttachment logic.
-						if (vi == vertexIndex) {
-							// Initial min should not block initial max.
-							// vi == vertexIndex does not always mean the bounds are fresh. It could be a submesh. Do not nuke old values by omitting the check.
-							// Should know that this is the first attachment in the submesh. slotIndex == startSlot could be an empty slot.
-							float fx = tempVerts[0], fy = tempVerts[1];
-							if (fx < bmin.x) bmin.x = fx;
-							if (fx > bmax.x) bmax.x = fx;
-							if (fy < bmin.y) bmin.y = fy;
-							if (fy > bmax.y) bmax.y = fy;
-						}
-
-						for (int iii = 0; iii < meshVertexCount; iii += 2) {
-							float x = tempVerts[iii], y = tempVerts[iii + 1];
-							verts[vi].x = x; verts[vi].y = y; verts[vi].z = z;
-							colors[vi] = color; uvs[vi].x = attachmentUVs[iii]; uvs[vi].y = attachmentUVs[iii + 1];
-
-							if (x < bmin.x) bmin.x = x;
-							else if (x > bmax.x) bmax.x = x;
-
-							if (y < bmin.y) bmin.y = y;
-							else if (y > bmax.y) bmax.y = y;
-
-							vi++;
-						}
-					}
-				}
-			}
-
-			// ref return values
-			vertexIndex = vi;
-			tempVertBuffer = tempVerts;
-			boundsMin = bmin;
-			boundsMax = bmax;
-		}
-
-
-		/// <summary>Fills a submesh triangle buffer array.</summary>
-		/// <param name="skeleton">Spine.Skeleton source of draw order slots.</param>
-		/// <param name="triangleCount">The target triangle count.</param>
-		/// <param name="firstVertex">First vertex of this submesh.</param>
-		/// <param name="startSlot">Start slot.</param>
-		/// <param name="endSlot">End slot.</param>
-		/// <param name="triangleBuffer">The triangle buffer array to be filled. This reference will be replaced in case the triangle values don't fit.</param>
-		/// <param name="isLastSubmesh">If set to <c>true</c>, the triangle buffer is allowed to be larger than needed.</param>
-		public static void FillTriangles (ref int[] triangleBuffer, Skeleton skeleton, int triangleCount, int firstVertex, int startSlot, int endSlot, bool isLastSubmesh) {
-			int trianglesCapacity = triangleBuffer.Length;
-			int[] tris = triangleBuffer;
-
-			if (isLastSubmesh) {
-				if (trianglesCapacity > triangleCount) {
-					for (int i = triangleCount; i < trianglesCapacity; i++)
-						tris[i] = 0;
-				} else if (trianglesCapacity < triangleCount) {
-					triangleBuffer = tris = new int[triangleCount];
-				}
-			} else if (trianglesCapacity != triangleCount) {
-				triangleBuffer = tris = new int[triangleCount];
-			}
-
-			var skeletonDrawOrderItems = skeleton.drawOrder.Items;
-			for (int i = startSlot, n = endSlot, ti = 0, afv = firstVertex; i < n; i++) {			
-				var attachment = skeletonDrawOrderItems[i].attachment;
-
-				// RegionAttachment
-				if (attachment is RegionAttachment) {
-					tris[ti] = afv;
-					tris[ti + 1] = afv + 2;
-					tris[ti + 2] = afv + 1;
-					tris[ti + 3] = afv + 2;
-					tris[ti + 4] = afv + 3;
-					tris[ti + 5] = afv + 1;
-					ti += 6;
-					afv += 4;
-					continue;
-				}
-
-				// MeshAttachment
-				var meshAttachment = attachment as MeshAttachment;
-				if (meshAttachment != null) {
-					int[] attachmentTriangles = meshAttachment.triangles;
-					for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, ti++)
-						tris[ti] = afv + attachmentTriangles[ii];
-
-					afv += meshAttachment.worldVerticesLength >> 1; // length/2;
-				}
-
-			}
-		}
-
-		public static void FillTrianglesQuads (ref int[] triangleBuffer, ref int storedTriangleCount, ref int storedFirstVertex, int instructionsFirstVertex, int instructionTriangleCount, bool isLastSubmesh) {
-			int trianglesCapacity = triangleBuffer.Length;
-
-			if (isLastSubmesh && trianglesCapacity > instructionTriangleCount) {
-				for (int i = instructionTriangleCount; i < trianglesCapacity; i++)
-					triangleBuffer[i] = 0;
-				storedTriangleCount = instructionTriangleCount;
-			} else if (trianglesCapacity != instructionTriangleCount) {
-				triangleBuffer = new int[instructionTriangleCount];
-				storedTriangleCount = 0;
-			}
-
-			// Use stored quad triangles if possible.
-			int[] tris = triangleBuffer;
-			if (storedFirstVertex != instructionsFirstVertex || storedTriangleCount < instructionTriangleCount) { //|| storedTriangleCount == 0
-				storedTriangleCount = instructionTriangleCount;
-				storedFirstVertex = instructionsFirstVertex;
-				int afv = instructionsFirstVertex; // attachment first vertex
-				for (int ti = 0; ti < instructionTriangleCount; ti += 6, afv += 4) {
-					tris[ti] = afv;
-					tris[ti + 1] = afv + 2;
-					tris[ti + 2] = afv + 1;
-					tris[ti + 3] = afv + 2;
-					tris[ti + 4] = afv + 3;
-					tris[ti + 5] = afv + 1;
-				}
-			}
-		}
-
-		/// <summary>Creates a UnityEngine.Bounds struct from minimum and maximum value vectors.</summary>
-		public static Bounds ToBounds (Vector3 boundsMin, Vector3 boundsMax) {
-			Vector3 size = (boundsMax - boundsMin);
-			return new Bounds((boundsMin + (size * 0.5f)), size);
-		}
-
-		#region TangentSolver2D
-		// Thanks to contributions from forum user ToddRivers
-
-		/// <summary>Step 1 of solving tangents. Ensure you have buffers of the correct size.</summary>
-		/// <param name="tangentBuffer">Eventual Vector4[] tangent buffer to assign to Mesh.tangents.</param>
-		/// <param name="tempTanBuffer">Temporary Vector2 buffer for calculating directions.</param>
-		/// <param name="vertexCount">Number of vertices that require tangents (or the size of the vertex array)</param>
-		public static void SolveTangents2DEnsureSize (ref Vector4[] tangentBuffer, ref Vector2[] tempTanBuffer, int vertexCount) {
-			if (tangentBuffer == null || tangentBuffer.Length < vertexCount)
-				tangentBuffer = new Vector4[vertexCount];
-			
-			if (tempTanBuffer == null || tempTanBuffer.Length < vertexCount * 2)
-				tempTanBuffer = new Vector2[vertexCount * 2]; // two arrays in one.
-		}
-
-		/// <summary>Step 2 of solving tangents. Fills (part of) a temporary tangent-solution buffer based on the vertices and uvs defined by a submesh's triangle buffer. Only needs to be called once for single-submesh meshes.</summary>
-		/// <param name="tempTanBuffer">A temporary Vector3[] for calculating tangents.</param>
-		/// <param name="vertices">The mesh's current vertex position buffer.</param>
-		/// <param name="triangles">The mesh's current triangles buffer.</param>
-		/// <param name="uvs">The mesh's current uvs buffer.</param>
-		/// <param name="vertexCount">Number of vertices that require tangents (or the size of the vertex array)</param>
-		/// <param name = "triangleCount">The number of triangle indexes in the triangle array to be used.</param>
-		public static void SolveTangents2DTriangles (Vector2[] tempTanBuffer, int[] triangles, int triangleCount, Vector3[] vertices, Vector2[] uvs, int vertexCount) {
-			Vector2 sdir;
-			Vector2 tdir;
-			for (int t = 0; t < triangleCount; t += 3) {
-				int i1 = triangles[t + 0];
-				int i2 = triangles[t + 1];
-				int i3 = triangles[t + 2];
-
-				Vector3 v1 = vertices[i1];
-				Vector3 v2 = vertices[i2];
-				Vector3 v3 = vertices[i3];
-
-				Vector2 w1 = uvs[i1];
-				Vector2 w2 = uvs[i2];
-				Vector2 w3 = uvs[i3];
-
-				float x1 = v2.x - v1.x;
-				float x2 = v3.x - v1.x;
-				float y1 = v2.y - v1.y;
-				float y2 = v3.y - v1.y;
-
-				float s1 = w2.x - w1.x;
-				float s2 = w3.x - w1.x;
-				float t1 = w2.y - w1.y;
-				float t2 = w3.y - w1.y;
-
-				float div = s1 * t2 - s2 * t1;
-				float r = (div == 0f) ? 0f : 1f / div;
-
-				sdir.x = (t2 * x1 - t1 * x2) * r;
-				sdir.y = (t2 * y1 - t1 * y2) * r;
-				tempTanBuffer[i1] = tempTanBuffer[i2] = tempTanBuffer[i3] = sdir;
-
-				tdir.x = (s1 * x2 - s2 * x1) * r;
-				tdir.y = (s1 * y2 - s2 * y1) * r;
-				tempTanBuffer[vertexCount + i1] = tempTanBuffer[vertexCount + i2] = tempTanBuffer[vertexCount + i3] = tdir;
-			}
-		}
-
-		/// <summary>Step 3 of solving tangents. Fills a Vector4[] tangents array according to values calculated in step 2.</summary>
-		/// <param name="tangents">A Vector4[] that will eventually be used to set Mesh.tangents</param>
-		/// <param name="tempTanBuffer">A temporary Vector3[] for calculating tangents.</param>
-		/// <param name="vertexCount">Number of vertices that require tangents (or the size of the vertex array)</param>
-		public static void SolveTangents2DBuffer (Vector4[] tangents, Vector2[] tempTanBuffer, int vertexCount) {
-
-			Vector4 tangent;
-			tangent.z = 0;
-			for (int i = 0; i < vertexCount; ++i) {
-				Vector2 t = tempTanBuffer[i]; 
-
-				// t.Normalize() (aggressively inlined). Even better if offloaded to GPU via vertex shader.
-				float magnitude = Mathf.Sqrt(t.x * t.x + t.y * t.y);
-				if (magnitude > 1E-05) {
-					float reciprocalMagnitude = 1f/magnitude;
-					t.x *= reciprocalMagnitude;
-					t.y *= reciprocalMagnitude;
-				}
-
-				Vector2 t2 = tempTanBuffer[vertexCount + i];
-				tangent.x = t.x;
-				tangent.y = t.y;
-				//tangent.z = 0;
-				tangent.w = (t.y * t2.x > t.x * t2.y) ? 1 : -1; // 2D direction calculation. Used for binormals.
-				tangents[i] = tangent;
-			}
-
-		}
-		#endregion
-
-		#region SubmeshTriangleBuffer
-		public class SubmeshTriangleBuffer {
-			public int[] triangles;
-			public int triangleCount; // for last/single submeshes with potentially zeroed triangles.
-			public int firstVertex = -1; // for !renderMeshes.
-
-			public SubmeshTriangleBuffer () { }
-
-			public SubmeshTriangleBuffer (int triangleCount) {
-				triangles = new int[triangleCount];
-			}
-		}
-		#endregion
-
-	}
-}

+ 0 - 144
spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSimpleMeshGenerator.cs

@@ -1,144 +0,0 @@
-/******************************************************************************
- * 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 UnityEngine;
-
-namespace Spine.Unity.MeshGeneration {
-	public class ArraysSimpleMeshGenerator : ArraysMeshGenerator, ISimpleMeshGenerator {
-		#region Settings
-		protected float scale = 1f;
-		public float Scale { get { return scale; } set { scale = value; } }
-		public float ZSpacing { get; set; }
-		#endregion
-
-		protected Mesh lastGeneratedMesh;
-		public Mesh LastGeneratedMesh {	get { return lastGeneratedMesh; } }
-
-		readonly DoubleBufferedMesh doubleBufferedMesh = new DoubleBufferedMesh();
-		int[] triangles;
-
-		public Mesh GenerateMesh (Skeleton skeleton) {
-			int totalVertexCount = 0; // size of vertex arrays
-			int totalTriangleCount = 0; // size of index array
-
-			// STEP 1 : GenerateInstruction(). Count verts and tris to determine array sizes.
-			var drawOrderItems = skeleton.drawOrder.Items;
-			int drawOrderCount = skeleton.drawOrder.Count;
-			for (int i = 0; i < drawOrderCount; i++) {
-				Slot slot = drawOrderItems[i];
-				Attachment attachment = slot.attachment;
-				int attachmentVertexCount, attachmentTriangleCount;
-				var regionAttachment = attachment as RegionAttachment;
-				if (regionAttachment != null) {					
-					attachmentVertexCount = 4;
-					attachmentTriangleCount = 6;
-				} else {
-					var meshAttachment = attachment as MeshAttachment;
-					if (meshAttachment != null) {
-						attachmentVertexCount = meshAttachment.worldVerticesLength >> 1;
-						attachmentTriangleCount = meshAttachment.triangles.Length;
-					} else {
-						continue;
-					}
-				}
-				totalTriangleCount += attachmentTriangleCount;
-				totalVertexCount += attachmentVertexCount;
-			}
-
-			// STEP 2 : Ensure buffers are the correct size
-			ArraysMeshGenerator.EnsureSize(totalVertexCount, ref this.meshVertices, ref this.meshUVs, ref this.meshColors32);
-			if (addBlackTint) {
-				ArraysMeshGenerator.EnsureSize(totalVertexCount, ref this.uv2);
-				ArraysMeshGenerator.EnsureSize(totalVertexCount, ref this.uv3);
-			}
-
-			this.triangles = this.triangles ?? new int[totalTriangleCount];
-				
-			// STEP 3 : Update vertex buffer
-			const float zFauxHalfThickness = 0.01f;	// Somehow needs this thickness for bounds to work properly in some cases (eg, Unity UI clipping)
-			Vector3 meshBoundsMin;
-			Vector3 meshBoundsMax;
-			if (totalVertexCount == 0) {
-				meshBoundsMin = new Vector3(0, 0, 0);
-				meshBoundsMax = new Vector3(0, 0, 0);
-			} else {
-				meshBoundsMin.x = int.MaxValue;
-				meshBoundsMin.y = int.MaxValue;
-				meshBoundsMax.x = int.MinValue;
-				meshBoundsMax.y = int.MinValue;
-				meshBoundsMin.z = -zFauxHalfThickness * scale;
-				meshBoundsMax.z = zFauxHalfThickness * scale;
-
-				int vertexIndex = 0;
-				if (addBlackTint) ArraysMeshGenerator.FillBlackUVs(skeleton, 0, drawOrderCount, this.uv2, this.uv3, vertexIndex);
-				ArraysMeshGenerator.FillVerts(skeleton, 0, drawOrderCount, this.ZSpacing, this.PremultiplyVertexColors, this.meshVertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax);
-
-				// Apply scale to vertices
-				meshBoundsMax.x *= scale; meshBoundsMax.y *= scale;
-				meshBoundsMin.x *= scale; meshBoundsMin.y *= scale;
-				var vertices = this.meshVertices;
-				for (int i = 0; i < totalVertexCount; i++) {
-					Vector3 p = vertices[i];
-					p.x *= scale;
-					p.y *= scale;
-					vertices[i] = p;
-				}
-			}
-				
-			// Step 4 : Update Triangles buffer
-			ArraysMeshGenerator.FillTriangles(ref this.triangles, skeleton, totalTriangleCount, 0, 0, drawOrderCount, true);
-
-			// Step 5 : Update Mesh with buffers
-			var mesh = doubleBufferedMesh.GetNextMesh();
-			mesh.vertices = this.meshVertices;
-			mesh.colors32 = meshColors32;
-			mesh.uv = meshUVs;
-			mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);
-			mesh.triangles = triangles;
-			if (addBlackTint) {
-				mesh.uv2 = this.uv2;
-				mesh.uv3 = this.uv3;
-			}
-
-			TryAddNormalsTo(mesh, totalVertexCount);
-
-			if (addTangents) { 
-				SolveTangents2DEnsureSize(ref this.meshTangents, ref this.tempTanBuffer, totalVertexCount);
-				SolveTangents2DTriangles(this.tempTanBuffer, triangles, totalTriangleCount, meshVertices, meshUVs, totalVertexCount);
-				SolveTangents2DBuffer(this.meshTangents, this.tempTanBuffer, totalVertexCount);
-			}
-
-			lastGeneratedMesh = mesh;
-			return mesh;
-		}
-
-	}
-
-}

+ 0 - 232
spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshSetMeshGenerator.cs

@@ -1,232 +0,0 @@
-/******************************************************************************
- * 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 UnityEngine;
-
-namespace Spine.Unity.MeshGeneration {
-	public class ArraysSubmeshSetMeshGenerator : ArraysMeshGenerator, ISubmeshSetMeshGenerator {
-		#region Settings
-		public float ZSpacing { get; set; }
-		#endregion
-
-		readonly DoubleBuffered<SmartMesh> doubleBufferedSmartMesh = new DoubleBuffered<SmartMesh>();
-		readonly ExposedList<SubmeshInstruction> currentInstructions = new ExposedList<SubmeshInstruction>();
-		readonly ExposedList<Attachment> attachmentBuffer = new ExposedList<Attachment>();
-		readonly ExposedList<SubmeshTriangleBuffer> submeshBuffers = new ExposedList<SubmeshTriangleBuffer>();
-		Material[] sharedMaterials = new Material[0];
-
-		/// <summary>
-		/// Generates a mesh based on a subset of instructions.
-		/// </summary>
-		/// <returns>A UnityEngine.Mesh.</returns>
-		/// <param name="instructions">A list of SubmeshInstructions.</param>
-		/// <param name="startSubmesh">The index of the starting submesh.</param>
-		/// <param name="endSubmesh">The exclusive upper bound of the last submesh to be included.</param>
-		public MeshAndMaterials GenerateMesh (ExposedList<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh, float scale = 1f) {
-			// STEP 0: Prepare instructions.
-			var paramItems = instructions.Items;
-			currentInstructions.Clear(false);
-			for (int i = startSubmesh, n = endSubmesh; i < n; i++) {
-				this.currentInstructions.Add(paramItems[i]);
-			}
-			var smartMesh = doubleBufferedSmartMesh.GetNext();
-			var mesh = smartMesh.mesh;
-			int submeshCount = currentInstructions.Count;
-			var currentInstructionsItems = currentInstructions.Items;
-			int vertexCount = 0;
-			for (int i = 0; i < submeshCount; i++) {
-				currentInstructionsItems[i].firstVertexIndex = vertexCount;// Ensure current instructions have correct cached values.
-				vertexCount += currentInstructionsItems[i].vertexCount; // vertexCount will also be used for the rest of this method.
-			}
-
-			// STEP 1: Ensure correct buffer sizes.
-			bool vertBufferResized = ArraysMeshGenerator.EnsureSize(vertexCount, ref this.meshVertices, ref this.meshUVs, ref this.meshColors32); 
-			bool submeshBuffersResized = ArraysMeshGenerator.EnsureTriangleBuffersSize(submeshBuffers, submeshCount, currentInstructionsItems);
-			if (addBlackTint) {
-				ArraysMeshGenerator.EnsureSize(vertexCount, ref this.uv2);
-				ArraysMeshGenerator.EnsureSize(vertexCount, ref this.uv3);
-			}
-
-			// STEP 2: Update buffers based on Skeleton.
-
-			// Initial values for manual Mesh Bounds calculation
-			Vector3 meshBoundsMin;
-			Vector3 meshBoundsMax;
-			float zSpacing = this.ZSpacing;
-			if (vertexCount <= 0) {
-				meshBoundsMin = new Vector3(0, 0, 0);
-				meshBoundsMax = new Vector3(0, 0, 0);
-			} else {
-				meshBoundsMin.x = int.MaxValue;
-				meshBoundsMin.y = int.MaxValue;
-				meshBoundsMax.x = int.MinValue;
-				meshBoundsMax.y = int.MinValue;
-
-				int endSlot = currentInstructionsItems[submeshCount - 1].endSlot;
-				if (zSpacing > 0f) {
-					meshBoundsMin.z = 0f;
-					meshBoundsMax.z = zSpacing * endSlot;
-				} else {
-					meshBoundsMin.z = zSpacing * endSlot;
-					meshBoundsMax.z = 0f;
-				}
-			}
-				
-			// For each submesh, add vertex data from attachments.
-			var workingAttachments = this.attachmentBuffer;
-			workingAttachments.Clear(false);
-			int vertexIndex = 0; // modified by FillVerts
-			for (int submeshIndex = 0; submeshIndex < submeshCount; submeshIndex++) {
-				var currentInstruction = currentInstructionsItems[submeshIndex];
-				int startSlot = currentInstruction.startSlot;
-				int endSlot = currentInstruction.endSlot;
-				var skeleton = currentInstruction.skeleton;
-				var skeletonDrawOrderItems = skeleton.DrawOrder.Items;
-				for (int i = startSlot; i < endSlot; i++) {
-					var ca = skeletonDrawOrderItems[i].attachment;
-					if (ca != null) workingAttachments.Add(ca); // Includes BoundingBoxes. This is ok.
-				}
-				if (addBlackTint) ArraysMeshGenerator.FillBlackUVs(skeleton, startSlot, endSlot, this.uv2, this.uv3, vertexIndex);
-				ArraysMeshGenerator.FillVerts(skeleton, startSlot, endSlot, zSpacing, this.PremultiplyVertexColors, this.meshVertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax);
-			}
-
-			bool structureDoesntMatch = vertBufferResized || submeshBuffersResized || smartMesh.StructureDoesntMatch(workingAttachments, currentInstructions);
-			for (int submeshIndex = 0; submeshIndex < submeshCount; submeshIndex++) {
-				var currentInstruction = currentInstructionsItems[submeshIndex];
-				if (structureDoesntMatch) {
-					var currentBuffer = submeshBuffers.Items[submeshIndex];
-					bool isLastSubmesh = (submeshIndex == submeshCount - 1);
-					ArraysMeshGenerator.FillTriangles(ref currentBuffer.triangles, currentInstruction.skeleton, currentInstruction.triangleCount, currentInstruction.firstVertexIndex, currentInstruction.startSlot, currentInstruction.endSlot, isLastSubmesh);
-					currentBuffer.triangleCount = currentInstruction.triangleCount;
-					currentBuffer.firstVertex = currentInstruction.firstVertexIndex;
-				}
-			}
-
-			if (structureDoesntMatch) {
-				mesh.Clear();
-				this.sharedMaterials = currentInstructions.GetUpdatedMaterialArray(this.sharedMaterials);
-			}
-
-			if (scale != 1f) {
-				for (int i = 0; i < vertexCount; i++) {
-					meshVertices[i].x *= scale;
-					meshVertices[i].y *= scale;
-					//meshVertices[i].z *= scale;
-				}
-			}
-
-			// STEP 3: Assign the buffers into the Mesh.
-			smartMesh.Set(this.meshVertices, this.meshUVs, this.meshColors32, workingAttachments, currentInstructions);
-			mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);
-			if (addBlackTint) {
-				mesh.uv2 = this.uv2;
-				mesh.uv3 = this.uv3;
-			}
-
-			if (structureDoesntMatch) {
-				// Push new triangles if doesn't match.
-				mesh.subMeshCount = submeshCount;
-				for (int i = 0; i < submeshCount; i++)
-					mesh.SetTriangles(submeshBuffers.Items[i].triangles, i);			
-
-				this.TryAddNormalsTo(mesh, vertexCount);
-			}
-
-			if (addTangents) { 
-				SolveTangents2DEnsureSize(ref this.meshTangents, ref this.tempTanBuffer, vertexCount);
-
-				for (int i = 0, n = submeshCount; i < n; i++) {
-					var submesh = submeshBuffers.Items[i];
-					SolveTangents2DTriangles(this.tempTanBuffer, submesh.triangles, submesh.triangleCount, meshVertices, meshUVs, vertexCount);
-				}
-					
-				SolveTangents2DBuffer(this.meshTangents, this.tempTanBuffer, vertexCount);
-			}
-				
-			return new MeshAndMaterials(smartMesh.mesh, sharedMaterials);
-		}
-
-		#region Types
-		// A SmartMesh is a Mesh (with submeshes) that knows what attachments and instructions were used to generate it.
-		class SmartMesh {
-			public readonly Mesh mesh = SpineMesh.NewMesh();
-			readonly ExposedList<Attachment> attachmentsUsed = new ExposedList<Attachment>();
-			readonly ExposedList<SubmeshInstruction> instructionsUsed = new ExposedList<SubmeshInstruction>();
-
-			public void Set (Vector3[] verts, Vector2[] uvs, Color32[] colors, ExposedList<Attachment> attachments, ExposedList<SubmeshInstruction> instructions) {
-				mesh.vertices = verts;
-				mesh.uv = uvs;
-				mesh.colors32 = colors;
-
-				attachmentsUsed.Clear(false);
-				attachmentsUsed.GrowIfNeeded(attachments.Capacity);
-				attachmentsUsed.Count = attachments.Count;
-				attachments.CopyTo(attachmentsUsed.Items);
-
-				instructionsUsed.Clear(false);
-				instructionsUsed.GrowIfNeeded(instructions.Capacity);
-				instructionsUsed.Count = instructions.Count;
-				instructions.CopyTo(instructionsUsed.Items);
-			}
-
-			public bool StructureDoesntMatch (ExposedList<Attachment> attachments, ExposedList<SubmeshInstruction> instructions) {
-				// Check count inequality.
-				if (attachments.Count != this.attachmentsUsed.Count) return true;
-				if (instructions.Count != this.instructionsUsed.Count) return true;
-
-				// Check each attachment.
-				var attachmentsPassed = attachments.Items;
-				var myAttachments = this.attachmentsUsed.Items;
-				for (int i = 0, n = attachmentsUsed.Count; i < n; i++)
-					if (attachmentsPassed[i] != myAttachments[i]) return true;
-
-				// Check each submesh for equal arrangement.
-				var instructionListItems = instructions.Items;
-				var myInstructions = this.instructionsUsed.Items;
-				for (int i = 0, n = this.instructionsUsed.Count; i < n; i++) {
-					var lhs = instructionListItems[i];
-					var rhs = myInstructions[i];
-					if (
-						lhs.material.GetInstanceID() != rhs.material.GetInstanceID() ||
-						lhs.startSlot != rhs.startSlot ||
-						lhs.endSlot != rhs.endSlot ||
-						lhs.triangleCount != rhs.triangleCount ||
-						lhs.vertexCount != rhs.vertexCount ||
-						lhs.firstVertexIndex != rhs.firstVertexIndex
-					) return true;
-				}
-
-				return false;
-			}
-		}
-		#endregion
-	}
-
-}

+ 0 - 315
spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshedMeshGenerator.cs

@@ -1,315 +0,0 @@
-/******************************************************************************
- * 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 UnityEngine;
-using System.Collections.Generic;
-
-namespace Spine.Unity.MeshGeneration {
-	/// <summary>
-	/// Arrays submeshed mesh generator.
-	/// </summary>
-	public class ArraysSubmeshedMeshGenerator : ArraysMeshGenerator, ISubmeshedMeshGenerator, System.IDisposable {
-
-		readonly List<Slot> separators = new List<Slot>();
-		public List<Slot> Separators { get { return this.separators; } }
-
-		#region Settings
-		public float ZSpacing { get; set; }
-		#endregion
-
-		readonly DoubleBuffered<SmartMesh> doubleBufferedSmartMesh = new DoubleBuffered<SmartMesh>();
-		readonly SubmeshedMeshInstruction currentInstructions = new SubmeshedMeshInstruction();
-		readonly ExposedList<SubmeshTriangleBuffer> submeshBuffers = new ExposedList<SubmeshTriangleBuffer>();
-		Material[] sharedMaterials = new Material[0];
-
-		public void Dispose () {
-			doubleBufferedSmartMesh.GetNext().Dispose();
-			doubleBufferedSmartMesh.GetNext().Dispose();
-		}
-
-		public SubmeshedMeshInstruction GenerateInstruction (Skeleton skeleton) {
-			if (skeleton == null) throw new System.ArgumentNullException("skeleton");
-
-			// Count vertices and submesh triangles.
-			int runningVertexCount = 0;
-
-			int submeshTriangleCount = 0;
-			int submeshFirstVertex = 0;
-			int submeshVertexCount = 0;
-			int submeshStartSlotIndex = 0;
-			Material lastMaterial = null;
-
-			var drawOrder = skeleton.drawOrder;
-			var drawOrderItems = drawOrder.Items;
-			int drawOrderCount = drawOrder.Count;
-			int separatorCount = separators.Count;
-
-			var instructionList = this.currentInstructions.submeshInstructions;
-			instructionList.Clear(false);
-
-			currentInstructions.attachmentList.Clear(false);
-
-			for (int i = 0; i < drawOrderCount; i++) {
-				var slot = drawOrderItems[i];
-				var attachment = slot.attachment;
-
-				object rendererObject; // An AtlasRegion in plain Spine-Unity. eventual source of Material object.
-				int attachmentVertexCount, attachmentTriangleCount;
-
-				var regionAttachment = attachment as RegionAttachment;
-				if (regionAttachment != null) {
-					rendererObject = regionAttachment.RendererObject;
-					attachmentVertexCount = 4;
-					attachmentTriangleCount = 6;
-				} else {
-					var meshAttachment = attachment as MeshAttachment;
-					if (meshAttachment != null) {
-						rendererObject = meshAttachment.RendererObject;
-						attachmentVertexCount = meshAttachment.worldVerticesLength >> 1;
-						attachmentTriangleCount = meshAttachment.triangles.Length;
-					} else {
-						continue;
-					}
-				}
-
-				var attachmentMaterial = (Material)((AtlasRegion)rendererObject).page.rendererObject;
-
-				// Populate submesh when material changes. (or when forced to separate by a submeshSeparator)
-				bool separatedBySlot  = ( separatorCount > 0 && separators.Contains(slot) );
-				if (( runningVertexCount > 0 && lastMaterial.GetInstanceID() != attachmentMaterial.GetInstanceID() ) ||	separatedBySlot) {
-
-					instructionList.Add(
-						new SubmeshInstruction {
-							skeleton = skeleton,
-							material = lastMaterial,
-							triangleCount = submeshTriangleCount,
-							vertexCount = submeshVertexCount,
-							startSlot = submeshStartSlotIndex,
-							endSlot = i,
-							firstVertexIndex = submeshFirstVertex,
-							forceSeparate = separatedBySlot
-						}
-					);
-
-					// Prepare for next submesh
-					submeshTriangleCount = 0;
-					submeshVertexCount = 0;
-					submeshFirstVertex = runningVertexCount;
-					submeshStartSlotIndex = i;
-				}
-				lastMaterial = attachmentMaterial;
-
-				submeshTriangleCount += attachmentTriangleCount;
-				submeshVertexCount += attachmentVertexCount;
-				runningVertexCount += attachmentVertexCount;
-
-				currentInstructions.attachmentList.Add(attachment);
-			}
-
-			instructionList.Add(
-				new SubmeshInstruction {
-					skeleton = skeleton,
-					material = lastMaterial,
-					triangleCount = submeshTriangleCount,
-					vertexCount = submeshVertexCount,
-					startSlot = submeshStartSlotIndex,
-					endSlot = drawOrderCount,
-					firstVertexIndex = submeshFirstVertex,
-					forceSeparate = false
-				}
-			);
-
-			currentInstructions.vertexCount = runningVertexCount;
-			return currentInstructions;
-		}
-
-		// ISubmeshedMeshGenerator.GenerateMesh
-		/// <summary>Generates a mesh based on SubmeshedMeshInstructions</summary>
-		public MeshAndMaterials GenerateMesh (SubmeshedMeshInstruction meshInstructions) {
-			var smartMesh = doubleBufferedSmartMesh.GetNext();
-			var mesh = smartMesh.mesh;
-			int submeshCount = meshInstructions.submeshInstructions.Count;
-			var instructionList = meshInstructions.submeshInstructions;
-
-			// STEP 1: Ensure correct buffer sizes.
-			int vertexCount = meshInstructions.vertexCount;
-			bool submeshBuffersResized = ArraysMeshGenerator.EnsureTriangleBuffersSize(submeshBuffers, submeshCount, instructionList.Items);
-			bool vertBufferResized = ArraysMeshGenerator.EnsureSize(vertexCount, ref this.meshVertices, ref this.meshUVs, ref this.meshColors32);
-			Vector3[] vertices = this.meshVertices;
-
-			if (addBlackTint) {
-				ArraysMeshGenerator.EnsureSize(vertexCount, ref this.uv2);
-				ArraysMeshGenerator.EnsureSize(vertexCount, ref this.uv3);
-			}
-
-			// STEP 2: Update buffers based on Skeleton.
-			float zSpacing = this.ZSpacing;
-			Vector3 meshBoundsMin;
-			Vector3 meshBoundsMax;
-			int attachmentCount = meshInstructions.attachmentList.Count;
-			if (attachmentCount <= 0) {
-				meshBoundsMin = new Vector3(0, 0, 0);
-				meshBoundsMax = new Vector3(0, 0, 0);
-			} else {
-				meshBoundsMin.x = int.MaxValue;
-				meshBoundsMin.y = int.MaxValue;
-				meshBoundsMax.x = int.MinValue;
-				meshBoundsMax.y = int.MinValue;
-
-				if (zSpacing > 0f) {
-					meshBoundsMin.z = 0f;
-					meshBoundsMax.z = zSpacing * (attachmentCount - 1);
-				} else {
-					meshBoundsMin.z = zSpacing * (attachmentCount - 1);
-					meshBoundsMax.z = 0f;
-				}
-			}
-			bool structureDoesntMatch = vertBufferResized || submeshBuffersResized || smartMesh.StructureDoesntMatch(meshInstructions);
-			// For each submesh, add vertex data from attachments. Also triangles, but only if needed.
-			int vertexIndex = 0; // modified by FillVerts
-			for (int submeshIndex = 0; submeshIndex < submeshCount; submeshIndex++) {
-				var submeshInstruction = instructionList.Items[submeshIndex];
-				int start = submeshInstruction.startSlot;
-				int end = submeshInstruction.endSlot;
-				var skeleton = submeshInstruction.skeleton;
-				if (addBlackTint) ArraysMeshGenerator.FillBlackUVs(skeleton, start, end, this.uv2, this.uv3, vertexIndex);
-				ArraysMeshGenerator.FillVerts(skeleton, start, end, zSpacing, this.PremultiplyVertexColors, vertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax);
-
-				if (structureDoesntMatch) {
-					var currentBuffer = submeshBuffers.Items[submeshIndex];
-					bool isLastSubmesh = (submeshIndex == submeshCount - 1);
-					ArraysMeshGenerator.FillTriangles(ref currentBuffer.triangles, skeleton, submeshInstruction.triangleCount, submeshInstruction.firstVertexIndex, start, end, isLastSubmesh);
-					currentBuffer.triangleCount = submeshInstruction.triangleCount;
-					currentBuffer.firstVertex = submeshInstruction.firstVertexIndex;
-				}
-			}
-
-			if (structureDoesntMatch) {
-				mesh.Clear();
-				this.sharedMaterials = meshInstructions.GetUpdatedMaterialArray(this.sharedMaterials);
-			}
-
-			// STEP 3: Assign the buffers into the Mesh.
-			smartMesh.Set(this.meshVertices, this.meshUVs, this.meshColors32, meshInstructions);
-			if (addBlackTint) {
-				mesh.uv2 = this.uv2;
-				mesh.uv3 = this.uv3;
-			}
-			mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);
-
-			if (structureDoesntMatch) {
-				// Push new triangles if doesn't match.
-				mesh.subMeshCount = submeshCount;
-				for (int i = 0; i < submeshCount; i++)
-					mesh.SetTriangles(submeshBuffers.Items[i].triangles, i);			
-
-				TryAddNormalsTo(mesh, vertexCount);
-			}
-
-			if (addTangents) { 
-				SolveTangents2DEnsureSize(ref this.meshTangents, ref this.tempTanBuffer, vertexCount);
-				for (int i = 0, n = submeshCount; i < n; i++) {
-					var submesh = submeshBuffers.Items[i];
-					SolveTangents2DTriangles(this.tempTanBuffer, submesh.triangles, submesh.triangleCount, meshVertices, meshUVs, vertexCount);
-				}
-				SolveTangents2DBuffer(this.meshTangents, this.tempTanBuffer, vertexCount);
-			}
-				
-			return new MeshAndMaterials(smartMesh.mesh, sharedMaterials);
-		}
-
-		#region Types
-		// A SmartMesh is a Mesh (with submeshes) that knows what attachments and instructions were used to generate it.
-		class SmartMesh : System.IDisposable {
-			public readonly Mesh mesh = SpineMesh.NewMesh();
-			readonly ExposedList<Attachment> attachmentsUsed = new ExposedList<Attachment>();
-			readonly ExposedList<SubmeshInstruction> instructionsUsed = new ExposedList<SubmeshInstruction>();
-
-			public void Dispose () {
-				if (mesh != null) {
-					if (Application.isEditor && !Application.isPlaying) {
-						UnityEngine.Object.DestroyImmediate(mesh);
-					} else {
-						UnityEngine.Object.Destroy(mesh);
-					}
-				}
-			}
-
-			public void Set (Vector3[] verts, Vector2[] uvs, Color32[] colors, SubmeshedMeshInstruction instruction) {
-				mesh.vertices = verts;
-				mesh.uv = uvs;
-				mesh.colors32 = colors;
-
-				attachmentsUsed.Clear(false);
-				attachmentsUsed.GrowIfNeeded(instruction.attachmentList.Capacity);
-				attachmentsUsed.Count = instruction.attachmentList.Count;
-				instruction.attachmentList.CopyTo(attachmentsUsed.Items);
-
-				instructionsUsed.Clear(false);
-				instructionsUsed.GrowIfNeeded(instruction.submeshInstructions.Capacity);
-				instructionsUsed.Count = instruction.submeshInstructions.Count;
-				instruction.submeshInstructions.CopyTo(instructionsUsed.Items);
-			}
-
-			public bool StructureDoesntMatch (SubmeshedMeshInstruction instructions) {
-				// Check count inequality.
-				if (instructions.attachmentList.Count != this.attachmentsUsed.Count) return true;
-				if (instructions.submeshInstructions.Count != this.instructionsUsed.Count) return true;
-
-				// Check each attachment.
-				var attachmentsPassed = instructions.attachmentList.Items;
-				var myAttachments = this.attachmentsUsed.Items;
-				for (int i = 0, n = attachmentsUsed.Count; i < n; i++)
-					if (attachmentsPassed[i] != myAttachments[i]) return true;
-
-				// Check each submesh for equal arrangement.
-				var instructionListItems = instructions.submeshInstructions.Items;
-				var myInstructions = this.instructionsUsed.Items;
-				for (int i = 0, n = this.instructionsUsed.Count; i < n; i++) {
-					var lhs = instructionListItems[i];
-					var rhs = myInstructions[i];
-					if (
-						lhs.material.GetInstanceID() != rhs.material.GetInstanceID() ||
-						lhs.startSlot != rhs.startSlot ||
-						lhs.endSlot != rhs.endSlot ||
-						lhs.triangleCount != rhs.triangleCount ||
-						lhs.vertexCount != rhs.vertexCount ||
-						lhs.firstVertexIndex != rhs.firstVertexIndex
-					) return true;
-				}
-
-				//Debug.Log("structure matched");
-				return false;
-			}
-		}
-		#endregion
-	}
-
-}

+ 0 - 45
spine-unity/Assets/spine-unity/Mesh Generation/DoubleBufferedMesh.cs

@@ -1,45 +0,0 @@
-/******************************************************************************
- * 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 UnityEngine;
-using System.Collections;
-
-namespace Spine.Unity {
-	public class DoubleBufferedMesh {
-		readonly Mesh mesh1 = SpineMesh.NewMesh();
-		readonly Mesh mesh2 = SpineMesh.NewMesh();
-		bool usingMesh1;
-			
-		public Mesh GetNextMesh () {
-			usingMesh1 = !usingMesh1;
-			return usingMesh1 ? mesh1 : mesh2;
-		}
-	}
-}

+ 0 - 48
spine-unity/Assets/spine-unity/Mesh Generation/ISimpleMeshGenerator.cs

@@ -1,48 +0,0 @@
-/******************************************************************************
- * 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.Unity.MeshGeneration { 
-	// Typically, each ISpineMeshGenerator implementation will handle double-buffering meshes, handling any other optimization behavior
-	// and operating on assumptions (eg, only handling one skeleton, not updating triangles all the time).
-	// The Scale property allows generated mesh to match external systems like Canvas referencePixelsPerUnit
-
-	public interface ISimpleMeshGenerator {
-		UnityEngine.Mesh GenerateMesh (Spine.Skeleton skeleton);
-		UnityEngine.Mesh LastGeneratedMesh { get; }
-
-		float Scale { set; }
-		float ZSpacing { get; set; }
-		bool PremultiplyVertexColors { get; set; }
-
-		bool AddNormals { get; set; }
-		bool AddTangents { get; set; }
-		bool AddBlackTint { get; set; }
-	}
-}

+ 0 - 125
spine-unity/Assets/spine-unity/Mesh Generation/ISubmeshedMeshGenerator.cs

@@ -1,125 +0,0 @@
-/******************************************************************************
- * 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 UnityEngine;
-using System.Collections.Generic;
-
-namespace Spine.Unity.MeshGeneration {
-	// ISubmeshedMeshGenerator:
-	// How to use:
-	// Step 1: Have a SubmeshedMeshGenerator instance, and a Spine.Skeleton
-	// Step 2: Call GenerateInstruction. Pass it your Skeleton. Keep the return value (a SubmeshedMeshInstruction, you can use it in other classes too).
-	// Step 3: Pass the SubmeshedMeshInstruction into GenerateMesh. You'll get a Mesh and Materials.
-	// Step 4: Put the Mesh in MeshFilter. Put the Materials in MeshRenderer.sharedMaterials.
-	public interface ISubmeshedMeshGenerator {
-		SubmeshedMeshInstruction GenerateInstruction (Skeleton skeleton);
-		MeshAndMaterials GenerateMesh (SubmeshedMeshInstruction wholeMeshInstruction);
-		List<Slot> Separators { get; }
-
-		float ZSpacing { get; set; }
-		bool PremultiplyVertexColors { get; set; }
-		bool AddNormals { get; set; }
-		bool AddTangents { get; set; }
-		bool AddBlackTint { get; set; }
-	}
-
-	// ISubmeshSetMeshGenerator
-	// How to use:
-	// Step 1: Get a list of SubmeshInstruction. You can get this from SkeletonRenderer or an ISubmeshedMeshGenerator's returned SubmeshedMeshInstruction.
-	// Step 2: Call AddInstruction one by one, or AddInstructions once.
-	// Step 3: Call GenerateMesh. You'll get a Mesh and Materials.
-	// Step 4: Put the Mesh in MeshFilter. Put the Materials in MeshRenderer.sharedMaterials.
-	public interface ISubmeshSetMeshGenerator {
-		MeshAndMaterials GenerateMesh (ExposedList<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh, float scale = 1f);
-
-		float ZSpacing { get; set; }
-		bool PremultiplyVertexColors { get; set; }
-		bool AddNormals { get; set; }
-		bool AddTangents { get; set; }
-		bool AddBlackTint { get; set; }
-	}
-
-	/// <summary>Primarily a collection of Submesh Instructions. This constitutes instructions for how to construct a mesh containing submeshes.</summary>
-	public class SubmeshedMeshInstruction {
-		public readonly ExposedList<SubmeshInstruction> submeshInstructions = new ExposedList<SubmeshInstruction>();
-		public readonly ExposedList<Attachment> attachmentList = new ExposedList<Attachment>();
-		public int vertexCount = -1;
-
-		/// <summary>Returns a material array of the SubmeshedMeshInstruction. Fills the passed array if it's the correct size. Creates a new array if it's a different size.</summary>
-		public Material[] GetUpdatedMaterialArray (Material[] materials) {
-			return submeshInstructions.GetUpdatedMaterialArray(materials);
-		}
-	}
-
-	/// <summary>Instructions for how to generate a mesh or submesh out of a range of slots in a given skeleton.</summary>
-	public struct SubmeshInstruction {
-		public Skeleton skeleton;
-		public int startSlot;
-		public int endSlot;
-
-		// Cached values because they are determined in the process of generating instructions,
-		// but could otherwise be pulled from accessing attachments, checking materials and counting tris and verts.
-		public Material material;
-		public int triangleCount;
-		public int vertexCount;
-
-		// Vertex index offset. Used by submesh generation if part of a bigger mesh.
-		public int firstVertexIndex;
-		public bool forceSeparate;
-
-		/// <summary>The number of slots in this SubmeshInstruction's range. Not necessarily the number of attachments.</summary>
-		public int SlotCount { get { return endSlot - startSlot; } }
-	}
-
-	public static class SubmeshInstructionExtensions {
-		/// <summary>Returns a material array of the instructions. Fills the passed array if it's the correct size. Creates a new array if it's a different size.</summary>
-		public static Material[] GetUpdatedMaterialArray (this ExposedList<SubmeshInstruction> instructions, Material[] materials) {
-			int submeshCount = instructions.Count;
-
-			if (submeshCount != materials.Length)
-				materials = new Material[submeshCount];
-
-			for (int i = 0, n = materials.Length; i < n; i++)
-				materials[i] = instructions.Items[i].material;
-
-			return materials;
-		}
-	}
-
-	public struct MeshAndMaterials {
-		public readonly Mesh mesh;
-		public readonly Material[] materials;
-
-		public MeshAndMaterials (Mesh mesh, Material[] materials) {
-			this.mesh = mesh;
-			this.materials = materials;
-		}
-	}
-}

+ 1123 - 1
spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs

@@ -28,11 +28,14 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
+//#define SPINE_TRIANGLECHECK // Avoid calling SetTriangles at the cost of checking for mesh differences (vertex counts, memberwise attachment list compare) every frame.
+
 using UnityEngine;
+using System;
+using System.Collections.Generic;
 
 namespace Spine.Unity {
 	public static class SpineMesh {
-
 		internal const HideFlags MeshHideflags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor;
 
 		/// <summary>Factory method for creating a new mesh for use in Spine components. This can be called in field initializers.</summary>
@@ -44,4 +47,1123 @@ namespace Spine.Unity {
 			return m;
 		}
 	}
+
+	/// <summary>Instructions for how to generate a mesh or submesh out of a range of slots in a given skeleton.</summary>
+	public struct SubmeshInstruction {
+		public Skeleton skeleton;
+		public int startSlot;
+		public int endSlot;
+
+		public Material material;
+		public bool forceSeparate;
+		public int preActiveClippingSlotSource;
+
+		#if SPINE_TRIANGLECHECK
+		// Cached values because they are determined in the process of generating instructions,
+		// but could otherwise be pulled from accessing attachments, checking materials and counting tris and verts.
+		public int rawTriangleCount;
+		public int rawVertexCount;
+		public int rawFirstVertexIndex;
+		public bool hasClipping;
+		#endif
+
+		/// <summary>The number of slots in this SubmeshInstruction's range. Not necessarily the number of attachments.</summary>
+		public int SlotCount { get { return endSlot - startSlot; } }
+	}
+
+	[System.Serializable]
+	public class MeshGenerator {
+		public Settings settings = Settings.Default;
+
+		[System.Serializable]
+		public struct Settings {
+			public bool renderMeshes;
+			public bool useClipping;
+			[Space]
+			[Range(-0.1f, 0f)] public float zSpacing;
+			[Space]
+			[Header("Vertex Data")]
+			public bool pmaVertexColors;
+			public bool tintBlack;
+			public bool calculateTangents;
+			public bool addNormals;
+			public bool immutableTriangles;
+
+			static public Settings Default {
+				get {
+					return new Settings {
+						pmaVertexColors = true,
+						zSpacing = 0f,
+						useClipping = true,
+						tintBlack = false,
+						calculateTangents = false,
+						renderMeshes = true,
+						addNormals = false,
+						immutableTriangles = false
+					};
+				}
+			}
+		}
+
+		const float BoundsMinDefault = float.MaxValue;
+		const float BoundsMaxDefault = float.MinValue;
+
+		[NonSerialized] readonly ExposedList<Vector3> vertexBuffer = new ExposedList<Vector3>();
+		[NonSerialized] readonly ExposedList<Vector2> uvBuffer = new ExposedList<Vector2>();
+		[NonSerialized] readonly ExposedList<Color32> colorBuffer = new ExposedList<Color32>();
+		[NonSerialized] readonly ExposedList<ExposedList<int>> submeshes = new ExposedList<ExposedList<int>> { new ExposedList<int>(6) }; // start with 1 submesh.
+
+		[NonSerialized] Vector2 meshBoundsMin, meshBoundsMax;
+		[NonSerialized] float meshBoundsThickness;
+		[NonSerialized] int submeshIndex = 0;
+
+		[NonSerialized] SkeletonClipping clipper = new SkeletonClipping();
+		[NonSerialized] float[] tempVerts = new float[8];
+		[NonSerialized] int[] regionTriangles = { 0, 1, 2, 2, 3, 0 };
+
+		#region Optional Buffers
+		[NonSerialized] Vector3[] normals;
+		[NonSerialized] Vector4[] tangents;
+		[NonSerialized] Vector2[] tempTanBuffer;
+		[NonSerialized] ExposedList<Vector2> uv2;
+		[NonSerialized] ExposedList<Vector2> uv3;
+		#endregion
+
+		#region Step 1 : Generate Instructions
+		public static void GenerateSingleSubmeshInstruction (SkeletonRendererInstruction instructionOutput, Skeleton skeleton, bool renderMeshes, Material material) {
+			ExposedList<Slot> drawOrder = skeleton.drawOrder;
+			int drawOrderCount = drawOrder.Count;
+
+			// Clear last state of attachments and submeshes
+			instructionOutput.Clear(); // submeshInstructions.Clear(); attachments.Clear();
+			var workingSubmeshInstructions = instructionOutput.submeshInstructions;
+			workingSubmeshInstructions.Resize(1);
+
+			#if SPINE_TRIANGLECHECK
+			instructionOutput.attachments.Resize(drawOrderCount);
+			var workingAttachmentsItems = instructionOutput.attachments.Items;
+			int totalRawVertexCount = 0;
+			#endif
+
+			var current = new SubmeshInstruction {
+				skeleton = skeleton,
+				preActiveClippingSlotSource = -1,
+				startSlot = 0,
+				#if SPINE_TRIANGLECHECK
+				rawFirstVertexIndex = 0,
+				#endif
+				material = material,
+				forceSeparate = false,
+				endSlot = drawOrderCount
+			};
+
+			#if SPINE_TRIANGLECHECK
+			bool skeletonHasClipping = false;
+			var drawOrderItems = drawOrder.Items;
+			for (int i = 0; i < drawOrderCount; i++) {
+				Slot slot = drawOrderItems[i];
+				Attachment attachment = slot.attachment;
+
+				workingAttachmentsItems[i] = attachment;
+				int attachmentTriangleCount = 0;
+				int attachmentVertexCount = 0;
+				
+
+				var regionAttachment = attachment as RegionAttachment;
+				if (regionAttachment != null) {
+					attachmentVertexCount = 4;
+					attachmentTriangleCount = 6;
+				} else {
+					if (renderMeshes) {
+						var meshAttachment = attachment as MeshAttachment;
+						if (meshAttachment != null) {
+							attachmentVertexCount = meshAttachment.worldVerticesLength >> 1;
+							attachmentTriangleCount = meshAttachment.triangles.Length;						
+						} else {
+							var clippingAttachment = attachment as ClippingAttachment;
+							if (clippingAttachment != null) {
+								current.hasClipping = true;
+								skeletonHasClipping = true;
+							}
+							attachmentVertexCount = 0;
+							attachmentTriangleCount = 0;
+							//continue;
+						}
+					}
+				}
+				current.rawTriangleCount += attachmentTriangleCount;
+				current.rawVertexCount += attachmentVertexCount;
+				totalRawVertexCount += attachmentVertexCount;
+				
+			}
+
+			instructionOutput.hasActiveClipping = skeletonHasClipping;
+			instructionOutput.rawVertexCount = totalRawVertexCount;
+			#endif
+
+			workingSubmeshInstructions.Items[0] = current;
+		}
+			
+		public static void GenerateSkeletonRendererInstruction (SkeletonRendererInstruction instructionOutput, Skeleton skeleton, Dictionary<Slot, Material> customSlotMaterials, List<Slot> separatorSlots, bool generateMeshOverride, bool immutableTriangles = false, bool renderMeshes = true) {
+//			if (skeleton == null) throw new ArgumentNullException("skeleton");
+//			if (instructionOutput == null) throw new ArgumentNullException("instructionOutput");
+
+			ExposedList<Slot> drawOrder = skeleton.drawOrder;
+			int drawOrderCount = drawOrder.Count;
+
+			// Clear last state of attachments and submeshes
+			instructionOutput.Clear(); // submeshInstructions.Clear(); attachments.Clear();
+			var workingSubmeshInstructions = instructionOutput.submeshInstructions;
+			#if SPINE_TRIANGLECHECK
+			instructionOutput.attachments.Resize(drawOrderCount);
+			var workingAttachmentsItems = instructionOutput.attachments.Items;
+			int totalRawVertexCount = 0;
+			bool skeletonHasClipping = false;
+			#endif
+
+			var current = new SubmeshInstruction {
+				skeleton = skeleton,
+				preActiveClippingSlotSource = -1
+			};
+
+			#if !SPINE_TK2D
+			bool isCustomSlotMaterialsPopulated = customSlotMaterials != null && customSlotMaterials.Count > 0;
+			#endif
+
+			int separatorCount = separatorSlots == null ? 0 : separatorSlots.Count;
+			bool hasSeparators = separatorCount > 0;
+
+			int clippingAttachmentSource = -1;
+			int lastPreActiveClipping = -1;
+			SlotData clippingEndSlot = null;
+			int submeshIndex = 0;
+			var drawOrderItems = drawOrder.Items;
+			bool currentHasRenderable = false;
+			for (int i = 0; i < drawOrderCount; i++) {
+				Slot slot = drawOrderItems[i];
+				Attachment attachment = slot.attachment;
+				#if SPINE_TRIANGLECHECK
+				workingAttachmentsItems[i] = attachment;
+				int attachmentVertexCount = 0, attachmentTriangleCount = 0;
+				#endif
+
+				object rendererObject = null; // An AtlasRegion in plain Spine-Unity. Spine-TK2D hooks into TK2D's system. eventual source of Material object.
+				bool noRender = false; // Using this allows empty slots as separators, and keeps separated parts more stable despite slots being reordered
+
+				var regionAttachment = attachment as RegionAttachment;
+				if (regionAttachment != null) {
+					rendererObject = regionAttachment.RendererObject;
+					#if SPINE_TRIANGLECHECK
+					attachmentVertexCount = 4;
+					attachmentTriangleCount = 6;
+					#endif
+					currentHasRenderable = true;
+				} else {
+					if (renderMeshes) {
+						var meshAttachment = attachment as MeshAttachment;
+						if (meshAttachment != null) {
+							rendererObject = meshAttachment.RendererObject;
+							#if SPINE_TRIANGLECHECK
+							attachmentVertexCount = meshAttachment.worldVerticesLength >> 1;
+							attachmentTriangleCount = meshAttachment.triangles.Length;
+							#endif
+							currentHasRenderable = true;
+						} else {
+							#if SPINE_TRIANGLECHECK
+							var clippingAttachment = attachment as ClippingAttachment;
+							if (clippingAttachment != null) {
+								clippingEndSlot = clippingAttachment.endSlot;
+								clippingAttachmentSource = i;
+								current.hasClipping = true;
+								skeletonHasClipping = true;
+							}
+							#endif
+							noRender = true;
+							//continue;
+						}
+					} else {
+						noRender = true;
+					}
+				}
+
+				if (clippingEndSlot != null && slot.data == clippingEndSlot) {
+					clippingEndSlot = null;
+					clippingAttachmentSource = -1;
+				}
+
+				// Create a new SubmeshInstruction when material changes. (or when forced to separate by a submeshSeparator)
+				// Slot with a separator/new material will become the starting slot of the next new instruction.
+				if (hasSeparators) { //current.forceSeparate = hasSeparators && separatorSlots.Contains(slot);
+					current.forceSeparate = false;
+					for (int s = 0; s < separatorCount; s++) {
+						if (Slot.ReferenceEquals(slot, separatorSlots[s])) {
+							current.forceSeparate = true;
+							break;
+						}
+					}
+				}
+
+				if (noRender) {
+					if (current.forceSeparate && currentHasRenderable && generateMeshOverride) {
+						{ // Add
+							current.endSlot = i;
+							current.preActiveClippingSlotSource = lastPreActiveClipping;
+
+							workingSubmeshInstructions.Resize(submeshIndex + 1);
+							workingSubmeshInstructions.Items[submeshIndex] = current;
+
+							submeshIndex++;
+						}
+						currentHasRenderable = false;
+						current.startSlot = i;
+						lastPreActiveClipping = clippingAttachmentSource;
+						#if SPINE_TRIANGLECHECK
+						current.rawTriangleCount = 0;
+						current.rawVertexCount = 0;
+						current.rawFirstVertexIndex = totalRawVertexCount;
+						current.hasClipping = clippingAttachmentSource >= 0;
+						#endif
+					}
+				} else {
+					#if !SPINE_TK2D
+					Material material;
+					if (isCustomSlotMaterialsPopulated) {
+						if (!customSlotMaterials.TryGetValue(slot, out material))
+							material = (Material)((AtlasRegion)rendererObject).page.rendererObject;				
+					} else {
+						material = (Material)((AtlasRegion)rendererObject).page.rendererObject;
+					}
+					#else
+					Material material = (rendererObject.GetType() == typeof(Material)) ? (Material)rendererObject : (Material)((AtlasRegion)rendererObject).page.rendererObject;
+					#endif
+
+					if (currentHasRenderable && (current.forceSeparate || !System.Object.ReferenceEquals(current.material, material))) { // Material changed. Add the previous submesh.
+						{ // Add
+							current.endSlot = i;
+							current.preActiveClippingSlotSource = lastPreActiveClipping;
+				
+							workingSubmeshInstructions.Resize(submeshIndex + 1);
+							workingSubmeshInstructions.Items[submeshIndex] = current;
+							submeshIndex++;
+						}
+						currentHasRenderable = false;
+						current.startSlot = i;
+						lastPreActiveClipping = clippingAttachmentSource;
+						#if SPINE_TRIANGLECHECK
+						current.rawTriangleCount = 0;
+						current.rawVertexCount = 0;
+						current.rawFirstVertexIndex = totalRawVertexCount;
+						current.hasClipping = clippingAttachmentSource >= 0;
+						#endif
+					}
+
+					// Update state for the next Attachment.
+					current.material = material;
+					#if SPINE_TRIANGLECHECK
+					current.rawTriangleCount += attachmentTriangleCount;
+					current.rawVertexCount += attachmentVertexCount;
+					current.rawFirstVertexIndex = totalRawVertexCount;
+					totalRawVertexCount += attachmentVertexCount;
+					#endif
+				}
+			}
+				
+			if (currentHasRenderable) {
+				{ // Add last or only submesh.
+					current.endSlot = drawOrderCount;
+					current.preActiveClippingSlotSource = lastPreActiveClipping;
+					current.forceSeparate = false;
+
+					workingSubmeshInstructions.Resize(submeshIndex + 1);
+					workingSubmeshInstructions.Items[submeshIndex] = current;
+					//submeshIndex++;	
+				}
+			}
+
+			#if SPINE_TRIANGLECHECK
+			instructionOutput.hasActiveClipping = skeletonHasClipping;
+			instructionOutput.rawVertexCount = totalRawVertexCount;
+			#endif
+			instructionOutput.immutableTriangles = immutableTriangles;
+		}
+
+		public static void TryReplaceMaterials (ExposedList<SubmeshInstruction> workingSubmeshInstructions, Dictionary<Material, Material> customMaterialOverride) {
+			// Material overrides are done here so they can be applied per submesh instead of per slot
+			// but they will still be passed through the GenerateMeshOverride delegate,
+			// and will still go through the normal material match check step in STEP 3.
+			var workingSubmeshInstructionsItems = workingSubmeshInstructions.Items;
+			for (int i = 0; i < workingSubmeshInstructions.Count; i++) {
+				var m = workingSubmeshInstructionsItems[i].material;
+				Material mo;
+				if (customMaterialOverride.TryGetValue(m, out mo))
+					workingSubmeshInstructionsItems[i].material = mo;
+			}
+		}
+		#endregion
+
+		#region Step 2 : Populate vertex data and triangle index buffers.
+		public void BeginNewMesh () {
+			vertexBuffer.Clear(false);
+			colorBuffer.Clear(false);
+			uvBuffer.Clear(false);
+			clipper.ClipEnd();
+
+			{
+				meshBoundsMin.x = BoundsMinDefault;
+				meshBoundsMin.y = BoundsMinDefault;
+				meshBoundsMax.x = BoundsMaxDefault;
+				meshBoundsMax.y = BoundsMaxDefault;
+				meshBoundsThickness = 0f;
+			}
+
+			submeshes.Count = 1;
+			submeshes.Items[0].Clear(false);
+			submeshIndex = 0;
+		}
+
+		public void AddSubmesh (SubmeshInstruction instruction) {
+			var settings = this.settings;
+			if (!settings.renderMeshes) {
+				AddSubmeshQuadsOnly(instruction);
+				return;
+			}
+
+			if (submeshes.Count - 1 < submeshIndex) {
+				submeshes.Resize(submeshIndex + 1);
+				if (submeshes.Items[submeshIndex] == null)
+					submeshes.Items[submeshIndex] = new ExposedList<int>();
+			}
+			var submesh = submeshes.Items[submeshIndex];
+			submesh.Clear(false);
+
+			var skeleton = instruction.skeleton;
+			var drawOrderItems = skeleton.drawOrder.Items;
+
+			Color32 color;
+			float skeletonA = skeleton.a * 255, skeletonR = skeleton.r, skeletonG = skeleton.g, skeletonB = skeleton.b;
+			Vector2 meshBoundsMin = this.meshBoundsMin, meshBoundsMax = this.meshBoundsMax;
+
+			// Settings
+			float zSpacing = settings.zSpacing;
+			#if SPINE_TRIANGLECHECK
+			bool useClipping = settings.useClipping && instruction.hasClipping;
+			#else
+			bool useClipping = settings.useClipping;
+			#endif
+
+			if (useClipping) {
+				if (instruction.preActiveClippingSlotSource >= 0) {
+					Debug.Log("PreActiveClipping");
+					var slot = drawOrderItems[instruction.preActiveClippingSlotSource];
+					clipper.ClipStart(slot, slot.attachment as ClippingAttachment);
+				}	
+			}
+
+			bool pmaVertexColors = settings.pmaVertexColors;
+			for (int slotIndex = instruction.startSlot; slotIndex < instruction.endSlot; slotIndex++) {
+				var slot = drawOrderItems[slotIndex];
+				var attachment = slot.attachment;
+				float z = zSpacing * slotIndex;
+
+				var workingVerts = this.tempVerts;
+				float[] uvs;
+				int[] attachmentTriangleIndices;
+				int attachmentVertexCount;
+				int attachmentIndexCount;
+
+				Color c = default(Color);
+
+				var region = attachment as RegionAttachment;
+				if (region != null) {
+					region.ComputeWorldVertices(slot.bone, workingVerts, 0);
+					uvs = region.uvs;
+					attachmentTriangleIndices = regionTriangles;
+					c.r = region.r; c.g = region.g; c.b = region.b; c.a = region.a;
+					attachmentVertexCount = 4;
+					attachmentIndexCount = 6;
+				} else {
+					var mesh = attachment as MeshAttachment;
+					if (mesh != null) {
+						int meshVertexCount = mesh.worldVerticesLength;
+						if (workingVerts.Length < meshVertexCount) {
+							workingVerts = new float[meshVertexCount];
+							this.tempVerts = workingVerts;
+						}
+						mesh.ComputeWorldVertices(slot, 0, meshVertexCount, workingVerts, 0); //meshAttachment.ComputeWorldVertices(slot, tempVerts);
+						uvs = mesh.uvs;
+						attachmentTriangleIndices = mesh.triangles;
+						c.r = mesh.r; c.g = mesh.g; c.b = mesh.b; c.a = mesh.a;
+						attachmentVertexCount = meshVertexCount >> 1; // meshVertexCount / 2;
+						attachmentIndexCount = mesh.triangles.Length;
+					} else {
+						if (useClipping) {
+							var clippingAttachment = attachment as ClippingAttachment;
+							if (clippingAttachment != null) {
+								clipper.ClipStart(slot, clippingAttachment);
+								continue;
+							}
+						}
+
+						continue;
+					}
+				}
+
+				if (pmaVertexColors) {
+					color.a = (byte)(skeletonA * slot.a * c.a);
+					color.r = (byte)(skeletonR * slot.r * c.r * color.a);
+					color.g = (byte)(skeletonG * slot.g * c.g * color.a);
+					color.b = (byte)(skeletonB * slot.b * c.b * color.a);
+					if (slot.data.blendMode == BlendMode.Additive) color.a = 0;
+				} else {
+					color.a = (byte)(skeletonA * slot.a * c.a);
+					color.r = (byte)(skeletonR * slot.r * c.r * 255);
+					color.g = (byte)(skeletonG * slot.g * c.g * 255);
+					color.b = (byte)(skeletonB * slot.b * c.b * 255);
+				}
+
+				if (useClipping && clipper.IsClipping()) {					
+					clipper.ClipTriangles(workingVerts, attachmentVertexCount << 1, attachmentTriangleIndices, attachmentIndexCount, uvs);
+					workingVerts = clipper.clippedVertices.Items;
+					attachmentVertexCount = clipper.clippedVertices.Count >> 1;
+					attachmentTriangleIndices = clipper.clippedTriangles.Items;
+					attachmentIndexCount = clipper.clippedTriangles.Count;
+					uvs = clipper.clippedUVs.Items;
+				}
+
+				if (attachmentVertexCount != 0 && attachmentIndexCount != 0) {
+					if (settings.tintBlack)
+						AddAttachmentTintBlack(slot.r2, slot.g2, slot.b2, attachmentVertexCount);
+
+					//AddAttachment(workingVerts, uvs, color, attachmentTriangleIndices, attachmentVertexCount, attachmentIndexCount, ref meshBoundsMin, ref meshBoundsMax, z);
+					int ovc = vertexBuffer.Count;
+					// Add data to vertex buffers
+					{
+						int newVertexCount = ovc + attachmentVertexCount;
+						if (newVertexCount > vertexBuffer.Items.Length) { // Manual ExposedList.Resize()
+							Array.Resize(ref vertexBuffer.Items, newVertexCount);
+							Array.Resize(ref uvBuffer.Items, newVertexCount);
+							Array.Resize(ref colorBuffer.Items, newVertexCount);
+						}
+						vertexBuffer.Count = uvBuffer.Count = colorBuffer.Count = newVertexCount;
+					}
+
+					var vbi = vertexBuffer.Items;
+					var ubi = uvBuffer.Items;
+					var cbi = colorBuffer.Items;
+					for (int i = 0; i < attachmentVertexCount; i++) {
+						int vi = ovc + i;
+						int i2 = i << 1; // i * 2
+						float x = workingVerts[i2];
+						float y = workingVerts[i2 + 1];
+
+						vbi[vi].x = x;
+						vbi[vi].y = y;
+						vbi[vi].z = z;
+						ubi[vi].x = uvs[i2];
+						ubi[vi].y = uvs[i2 + 1];
+						cbi[vi] = color;
+
+						// Calculate bounds.
+						if (x < meshBoundsMin.x) meshBoundsMin.x = x;
+						else if (x > meshBoundsMax.x) meshBoundsMax.x = x;
+						if (y < meshBoundsMin.y) meshBoundsMin.y = y;
+						else if (y > meshBoundsMax.y) meshBoundsMax.y = y;
+					}
+
+					// Add data to triangle buffer
+					int oldTriangleCount = submesh.Count;
+					{ //submesh.Resize(oldTriangleCount + attachmentIndexCount);
+						int newTriangleCount = oldTriangleCount + attachmentIndexCount;
+						if (newTriangleCount > submesh.Items.Length) Array.Resize(ref submesh.Items, newTriangleCount); 
+						submesh.Count = newTriangleCount;
+					}
+					var submeshItems = submesh.Items;
+					for (int i = 0; i < attachmentIndexCount; i++)
+						submeshItems[oldTriangleCount + i] = attachmentTriangleIndices[i] + ovc;
+				}
+
+				clipper.ClipEnd(slot);
+			}
+			clipper.ClipEnd();
+				
+			this.meshBoundsMin = meshBoundsMin;
+			this.meshBoundsMax = meshBoundsMax;
+			meshBoundsThickness = instruction.endSlot * zSpacing;
+
+			// Trim or zero submesh triangles.
+			var currentSubmeshItems = submesh.Items;
+			for (int i = submesh.Count, n = currentSubmeshItems.Length; i < n; i++)
+				currentSubmeshItems[i] = 0;
+
+			// Next AddSubmesh will use a new submeshIndex value.
+			submeshIndex++;
+		}
+
+		public void ScaleVertexData (float scale) {
+			var vbi = vertexBuffer.Items;
+			for (int i = 0, n = vertexBuffer.Count; i < n; i++) {
+//				vbi[i].x *= scale;
+//				vbi[i].y *= scale;
+				vbi[i] *= scale;
+			}
+
+			meshBoundsMin *= scale;
+			meshBoundsMax *= scale;
+			meshBoundsThickness *= scale;
+		}
+
+		void AddSubmeshQuadsOnly (SubmeshInstruction instruction) {
+			const int attachmentVertexCount = 4;
+			const int attachmentIndexCount = 6;
+			int[] attachmentTriangleIndices = regionTriangles;
+
+			var settings = this.settings;
+
+			if (submeshes.Count - 1 < submeshIndex) {
+				submeshes.Resize(submeshIndex + 1);
+				if (submeshes.Items[submeshIndex] == null)
+					submeshes.Items[submeshIndex] = new ExposedList<int>();
+			}
+			var submesh = submeshes.Items[submeshIndex];
+			submesh.Clear(false);
+
+			var skeleton = instruction.skeleton;
+			var drawOrderItems = skeleton.drawOrder.Items;
+
+			Color32 color;
+			float skeletonA = skeleton.a * 255, skeletonR = skeleton.r, skeletonG = skeleton.g, skeletonB = skeleton.b;
+			Vector2 meshBoundsMin = this.meshBoundsMin, meshBoundsMax = this.meshBoundsMax;
+
+			// Settings
+			float zSpacing = settings.zSpacing;
+
+			bool pmaVertexColors = settings.pmaVertexColors;
+			for (int slotIndex = instruction.startSlot; slotIndex < instruction.endSlot; slotIndex++) {
+				var slot = drawOrderItems[slotIndex];
+				var attachment = slot.attachment;
+				float z = zSpacing * slotIndex;
+
+				var workingVerts = this.tempVerts;
+				float[] uvs;
+
+				Color c = default(Color);
+
+				var region = attachment as RegionAttachment;
+				if (region != null) {
+					region.ComputeWorldVertices(slot.bone, workingVerts, 0);
+					uvs = region.uvs;
+					c.r = region.r; c.g = region.g; c.b = region.b; c.a = region.a;
+				} else {
+					continue;
+				}
+
+				if (pmaVertexColors) {
+					color.a = (byte)(skeletonA * slot.a * c.a);
+					color.r = (byte)(skeletonR * slot.r * c.r * color.a);
+					color.g = (byte)(skeletonG * slot.g * c.g * color.a);
+					color.b = (byte)(skeletonB * slot.b * c.b * color.a);
+					if (slot.data.blendMode == BlendMode.Additive) color.a = 0;
+				} else {
+					color.a = (byte)(skeletonA * slot.a * c.a);
+					color.r = (byte)(skeletonR * slot.r * c.r * 255);
+					color.g = (byte)(skeletonG * slot.g * c.g * 255);
+					color.b = (byte)(skeletonB * slot.b * c.b * 255);
+				}
+
+				{
+					if (settings.tintBlack)
+						AddAttachmentTintBlack(slot.r2, slot.g2, slot.b2, attachmentVertexCount);
+
+					//AddAttachment(workingVerts, uvs, color, attachmentTriangleIndices, attachmentVertexCount, attachmentIndexCount, ref meshBoundsMin, ref meshBoundsMax, z);
+					int ovc = vertexBuffer.Count;
+					// Add data to vertex buffers
+					{
+						int newVertexCount = ovc + attachmentVertexCount;
+						if (newVertexCount > vertexBuffer.Items.Length) { // Manual ExposedList.Resize()
+							Array.Resize(ref vertexBuffer.Items, newVertexCount);
+							Array.Resize(ref uvBuffer.Items, newVertexCount);
+							Array.Resize(ref colorBuffer.Items, newVertexCount);
+						}
+						vertexBuffer.Count = uvBuffer.Count = colorBuffer.Count = newVertexCount;
+					}
+
+					var vbi = vertexBuffer.Items;
+					var ubi = uvBuffer.Items;
+					var cbi = colorBuffer.Items;
+					for (int i = 0; i < attachmentVertexCount; i++) {
+						int vi = ovc + i;
+						int i2 = i << 1; // i * 2
+						float x = workingVerts[i2];
+						float y = workingVerts[i2 + 1];
+
+						vbi[vi].x = x;
+						vbi[vi].y = y;
+						vbi[vi].z = z;
+						ubi[vi].x = uvs[i2];
+						ubi[vi].y = uvs[i2 + 1];
+						cbi[vi] = color;
+
+						// Calculate bounds.
+						if (x < meshBoundsMin.x) meshBoundsMin.x = x;
+						else if (x > meshBoundsMax.x) meshBoundsMax.x = x;
+						if (y < meshBoundsMin.y) meshBoundsMin.y = y;
+						else if (y > meshBoundsMax.y) meshBoundsMax.y = y;
+					}
+
+					// TODO: Simplify triangle buffer handling.
+					// Add data to triangle buffer
+					int oldTriangleCount = submesh.Count;
+					{ //submesh.Resize(oldTriangleCount + attachmentIndexCount);
+						int newTriangleCount = oldTriangleCount + attachmentIndexCount;
+						if (newTriangleCount > submesh.Items.Length) Array.Resize(ref submesh.Items, newTriangleCount); 
+						submesh.Count = newTriangleCount;
+					}
+					var submeshItems = submesh.Items;
+					for (int i = 0; i < attachmentIndexCount; i++)
+						submeshItems[oldTriangleCount + i] = attachmentTriangleIndices[i] + ovc;
+				}
+			}
+
+			this.meshBoundsMin = meshBoundsMin;
+			this.meshBoundsMax = meshBoundsMax;
+			meshBoundsThickness = instruction.endSlot * zSpacing;
+
+			// Trim or zero submesh triangles.
+			var currentSubmeshItems = submesh.Items;
+			for (int i = submesh.Count, n = currentSubmeshItems.Length; i < n; i++)
+				currentSubmeshItems[i] = 0;
+
+			// Next AddSubmesh will use a new submeshIndex value.
+			submeshIndex++;
+		}
+
+		void AddAttachmentTintBlack (float r2, float g2, float b2, int vertexCount) {
+			var rg = new Vector2(r2, g2);
+			var bo = new Vector2(b2, 1f);
+
+			int ovc = vertexBuffer.Count;				
+			int newVertexCount = ovc + vertexCount;
+			{ 
+				if (uv2 == null) {
+					uv2 = new ExposedList<Vector2>();
+					uv3 = new ExposedList<Vector2>();
+				}
+				if (newVertexCount > uv2.Items.Length) { // Manual ExposedList.Resize()
+					Array.Resize(ref uv2.Items, newVertexCount);
+					Array.Resize(ref uv3.Items, newVertexCount);
+				}
+				uv2.Count = uv3.Count = newVertexCount;
+			}
+
+			var uv2i = uv2.Items;
+			var uv3i = uv3.Items;
+			for (int i = 0; i < vertexCount; i++) {
+				uv2i[ovc + i] = rg;
+				uv3i[ovc + i] = bo;
+			}
+		}
+		#endregion
+
+		#region Step 3 : Transfer vertex and triangle data to UnityEngine.Mesh
+		public void FillVertexData (Mesh mesh) {
+			var vbi = vertexBuffer.Items;
+			var ubi = uvBuffer.Items;
+			var cbi = colorBuffer.Items;
+			var sbi = submeshes.Items;
+			int submeshCount = submeshes.Count;
+
+			// Zero the extra.
+			{
+				int listCount = vertexBuffer.Count;
+				int arrayLength = vertexBuffer.Items.Length;
+				var vector3zero = Vector3.zero;
+				for (int i = listCount; i < arrayLength; i++)
+					vbi[i] = vector3zero;
+			}
+
+			// Set the vertex buffer.
+			{
+				mesh.vertices = vbi;
+				mesh.uv = ubi;
+				mesh.colors32 = cbi;
+
+				if (meshBoundsMin.x == BoundsMinDefault) {
+					mesh.bounds = new Bounds();
+				} else {
+					Vector2 halfSize = (meshBoundsMax - meshBoundsMin) * 0.5f;
+					mesh.bounds = new Bounds {
+						center = (Vector3)(meshBoundsMin + halfSize),
+						extents = new Vector3(halfSize.x, halfSize.y, meshBoundsThickness * 0.5f)
+					};
+					//mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);
+				}
+			}
+
+			{
+				int vertexCount = this.vertexBuffer.Count;
+				if (settings.addNormals) {
+					int oldLength = 0;
+
+					if (normals == null)
+						normals = new Vector3[vertexCount];	
+					else
+						oldLength = normals.Length;
+
+					if (oldLength < vertexCount) {
+						Array.Resize(ref this.normals, vertexCount);
+						var localNormals = this.normals;
+						for (int i = oldLength; i < vertexCount; i++) localNormals[i] = Vector3.back;
+					}
+					mesh.normals = this.normals;
+				}
+
+				if (settings.tintBlack) {
+					mesh.uv2 = this.uv2.Items;
+					mesh.uv3 = this.uv3.Items;
+				}
+
+				if (settings.calculateTangents) {
+					MeshGenerator.SolveTangents2DEnsureSize(ref this.tangents, ref this.tempTanBuffer, vertexCount);
+					for (int i = 0; i < submeshCount; i++) {
+						var submesh = sbi[i].Items;
+						int triangleCount = sbi[i].Count;
+						MeshGenerator.SolveTangents2DTriangles(this.tempTanBuffer, submesh, triangleCount, vbi, ubi, vertexCount);
+					}
+					MeshGenerator.SolveTangents2DBuffer(this.tangents, this.tempTanBuffer, vertexCount);
+					mesh.tangents = this.tangents;
+				}
+			}
+		}
+
+		public void FillTriangles (Mesh mesh) {
+			int submeshCount = submeshes.Count;
+			var submeshesItems = submeshes.Items;
+			mesh.subMeshCount = submeshCount;
+
+			for (int i = 0; i < submeshCount; i++)
+				mesh.SetTriangles(submeshesItems[i].Items, i, false);				
+		}
+
+		public void FillTrianglesSingle (Mesh mesh) {
+			mesh.SetTriangles(submeshes.Items[0].Items, 0, false);
+		}
+		#endregion
+
+		public void TrimExcess () {
+			vertexBuffer.TrimExcess();
+			uvBuffer.TrimExcess();
+			colorBuffer.TrimExcess();
+
+			if (uv2 != null) uv2.TrimExcess();
+			if (uv3 != null) uv3.TrimExcess();
+
+			int count = vertexBuffer.Count;
+			if (normals != null) Array.Resize(ref normals, count);
+			if (tangents != null) Array.Resize(ref tangents, count);
+		}
+
+		#region TangentSolver2D
+		// Thanks to contributions from forum user ToddRivers
+
+		/// <summary>Step 1 of solving tangents. Ensure you have buffers of the correct size.</summary>
+		/// <param name="tangentBuffer">Eventual Vector4[] tangent buffer to assign to Mesh.tangents.</param>
+		/// <param name="tempTanBuffer">Temporary Vector2 buffer for calculating directions.</param>
+		/// <param name="vertexCount">Number of vertices that require tangents (or the size of the vertex array)</param>
+		internal static void SolveTangents2DEnsureSize (ref Vector4[] tangentBuffer, ref Vector2[] tempTanBuffer, int vertexCount) {
+			if (tangentBuffer == null || tangentBuffer.Length < vertexCount)
+				tangentBuffer = new Vector4[vertexCount];
+
+			if (tempTanBuffer == null || tempTanBuffer.Length < vertexCount * 2)
+				tempTanBuffer = new Vector2[vertexCount * 2]; // two arrays in one.
+		}
+
+		/// <summary>Step 2 of solving tangents. Fills (part of) a temporary tangent-solution buffer based on the vertices and uvs defined by a submesh's triangle buffer. Only needs to be called once for single-submesh meshes.</summary>
+		/// <param name="tempTanBuffer">A temporary Vector3[] for calculating tangents.</param>
+		/// <param name="vertices">The mesh's current vertex position buffer.</param>
+		/// <param name="triangles">The mesh's current triangles buffer.</param>
+		/// <param name="uvs">The mesh's current uvs buffer.</param>
+		/// <param name="vertexCount">Number of vertices that require tangents (or the size of the vertex array)</param>
+		/// <param name = "triangleCount">The number of triangle indexes in the triangle array to be used.</param>
+		internal static void SolveTangents2DTriangles (Vector2[] tempTanBuffer, int[] triangles, int triangleCount, Vector3[] vertices, Vector2[] uvs, int vertexCount) {
+			Vector2 sdir;
+			Vector2 tdir;
+			for (int t = 0; t < triangleCount; t += 3) {
+				int i1 = triangles[t + 0];
+				int i2 = triangles[t + 1];
+				int i3 = triangles[t + 2];
+
+				Vector3 v1 = vertices[i1];
+				Vector3 v2 = vertices[i2];
+				Vector3 v3 = vertices[i3];
+
+				Vector2 w1 = uvs[i1];
+				Vector2 w2 = uvs[i2];
+				Vector2 w3 = uvs[i3];
+
+				float x1 = v2.x - v1.x;
+				float x2 = v3.x - v1.x;
+				float y1 = v2.y - v1.y;
+				float y2 = v3.y - v1.y;
+
+				float s1 = w2.x - w1.x;
+				float s2 = w3.x - w1.x;
+				float t1 = w2.y - w1.y;
+				float t2 = w3.y - w1.y;
+
+				float div = s1 * t2 - s2 * t1;
+				float r = (div == 0f) ? 0f : 1f / div;
+
+				sdir.x = (t2 * x1 - t1 * x2) * r;
+				sdir.y = (t2 * y1 - t1 * y2) * r;
+				tempTanBuffer[i1] = tempTanBuffer[i2] = tempTanBuffer[i3] = sdir;
+
+				tdir.x = (s1 * x2 - s2 * x1) * r;
+				tdir.y = (s1 * y2 - s2 * y1) * r;
+				tempTanBuffer[vertexCount + i1] = tempTanBuffer[vertexCount + i2] = tempTanBuffer[vertexCount + i3] = tdir;
+			}
+		}
+
+		/// <summary>Step 3 of solving tangents. Fills a Vector4[] tangents array according to values calculated in step 2.</summary>
+		/// <param name="tangents">A Vector4[] that will eventually be used to set Mesh.tangents</param>
+		/// <param name="tempTanBuffer">A temporary Vector3[] for calculating tangents.</param>
+		/// <param name="vertexCount">Number of vertices that require tangents (or the size of the vertex array)</param>
+		internal static void SolveTangents2DBuffer (Vector4[] tangents, Vector2[] tempTanBuffer, int vertexCount) {
+
+			Vector4 tangent;
+			tangent.z = 0;
+			for (int i = 0; i < vertexCount; ++i) {
+				Vector2 t = tempTanBuffer[i]; 
+
+				// t.Normalize() (aggressively inlined). Even better if offloaded to GPU via vertex shader.
+				float magnitude = Mathf.Sqrt(t.x * t.x + t.y * t.y);
+				if (magnitude > 1E-05) {
+					float reciprocalMagnitude = 1f/magnitude;
+					t.x *= reciprocalMagnitude;
+					t.y *= reciprocalMagnitude;
+				}
+
+				Vector2 t2 = tempTanBuffer[vertexCount + i];
+				tangent.x = t.x;
+				tangent.y = t.y;
+				//tangent.z = 0;
+				tangent.w = (t.y * t2.x > t.x * t2.y) ? 1 : -1; // 2D direction calculation. Used for binormals.
+				tangents[i] = tangent;
+			}
+
+		}
+		#endregion
+	}
+
+	public class MeshRendererBuffers : IDisposable {
+		DoubleBuffered<SmartMesh> doubleBufferedMesh;
+		internal readonly ExposedList<Material> submeshMaterials = new ExposedList<Material>();
+		internal Material[] sharedMaterials = new Material[0];
+
+		public void Initialize () {
+			doubleBufferedMesh = new DoubleBuffered<SmartMesh>();
+		}
+
+		public Material[] GetUpdatedShaderdMaterialsArray () {
+			if (submeshMaterials.Count == sharedMaterials.Length)
+				submeshMaterials.CopyTo(sharedMaterials);
+			else
+				sharedMaterials = submeshMaterials.ToArray();
+
+			return sharedMaterials;
+		}
+
+		public bool MaterialsChangedInLastUpdate () {
+			int newSubmeshMaterials = submeshMaterials.Count;
+			var sharedMaterials = this.sharedMaterials;
+			if (newSubmeshMaterials != sharedMaterials.Length) return true;
+
+			var submeshMaterialsItems = submeshMaterials.Items;
+			for (int i = 0; i < newSubmeshMaterials; i++)
+				if (!Material.ReferenceEquals(submeshMaterialsItems[i], sharedMaterials[i])) return true; //if (submeshMaterialsItems[i].GetInstanceID() != sharedMaterials[i].GetInstanceID()) return true;
+
+			return false;
+		}
+
+		public void UpdateSharedMaterials (ExposedList<SubmeshInstruction> instructions) {
+			int newSize = instructions.Count;
+			{ //submeshMaterials.Resize(instructions.Count);
+				if (newSize > submeshMaterials.Items.Length)
+					Array.Resize(ref submeshMaterials.Items, newSize);
+				submeshMaterials.Count = newSize;
+			}
+
+			var submeshMaterialsItems = submeshMaterials.Items;
+			var instructionsItems = instructions.Items;
+			for (int i = 0; i < newSize; i++)
+				submeshMaterialsItems[i] = instructionsItems[i].material;
+		}
+
+		public SmartMesh GetNextMesh () {
+			return doubleBufferedMesh.GetNext();
+		}
+
+		public void Clear () {
+			sharedMaterials = new Material[0];
+			submeshMaterials.Clear();
+		}
+
+		public void Dispose () {
+			if (doubleBufferedMesh == null) return;
+			doubleBufferedMesh.GetNext().Dispose();
+			doubleBufferedMesh.GetNext().Dispose();
+			doubleBufferedMesh = null;
+		}
+
+		///<summary>This is a Mesh that also stores the instructions SkeletonRenderer generated for it.</summary>
+		public class SmartMesh : IDisposable {
+			public Mesh mesh = SpineMesh.NewMesh();
+			public SkeletonRendererInstruction instructionUsed = new SkeletonRendererInstruction();		
+
+			public void Dispose () {
+				if (mesh != null) {
+					#if UNITY_EDITOR
+					if (Application.isEditor && !Application.isPlaying)
+						UnityEngine.Object.DestroyImmediate(mesh);
+					else
+						UnityEngine.Object.Destroy(mesh);
+					#else
+					UnityEngine.Object.Destroy(mesh);
+					#endif
+				}
+				mesh = null;
+			}
+		}
+	}
+
+	public class SkeletonRendererInstruction {
+		public bool immutableTriangles;
+		public readonly ExposedList<SubmeshInstruction> submeshInstructions = new ExposedList<SubmeshInstruction>();
+
+		#if SPINE_TRIANGLECHECK
+		public bool hasActiveClipping;
+		public int rawVertexCount = -1;
+		public readonly ExposedList<Attachment> attachments = new ExposedList<Attachment>();
+		#endif
+
+		public void Clear () {
+			#if SPINE_TRIANGLECHECK
+			this.attachments.Clear(false);
+			rawVertexCount = -1;
+			hasActiveClipping = false;
+			#endif
+			this.submeshInstructions.Clear(false);
+		}
+
+		public void SetWithSubset (ExposedList<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh) {
+			#if SPINE_TRIANGLECHECK
+			int runningVertexCount = 0;
+			#endif
+
+			var submeshes = this.submeshInstructions;
+			submeshes.Clear(false);
+			int submeshCount = endSubmesh - startSubmesh;
+			submeshes.Resize(submeshCount);
+			var submeshesItems = submeshes.Items;
+			var instructionsItems = instructions.Items;
+			for (int i = 0; i < submeshCount; i++) {
+				var instruction = instructionsItems[startSubmesh + i];
+				submeshesItems[i] = instruction;
+				#if SPINE_TRIANGLECHECK
+				this.hasActiveClipping = instruction.hasClipping;
+				submeshesItems[i].rawFirstVertexIndex = runningVertexCount; // Ensure current instructions have correct cached values.
+				runningVertexCount += instruction.rawVertexCount; // vertexCount will also be used for the rest of this method.
+				#endif
+			}
+			#if SPINE_TRIANGLECHECK
+			this.rawVertexCount = runningVertexCount;
+
+			// assumption: instructions are contiguous. start and end are valid within instructions.
+
+			int startSlot = instructionsItems[startSubmesh].startSlot;
+			int endSlot = instructionsItems[endSubmesh - 1].endSlot;
+			attachments.Clear(false);
+			int attachmentCount = endSlot - startSlot;
+			attachments.Resize(attachmentCount);
+			var attachmentsItems = attachments.Items;
+
+			var drawOrder = instructionsItems[0].skeleton.drawOrder.Items;
+			for (int i = 0; i < attachmentCount; i++)
+				attachmentsItems[i] = drawOrder[startSlot + i].attachment;
+			#endif
+		}
+
+		public void Set (SkeletonRendererInstruction other) {
+			this.immutableTriangles = other.immutableTriangles;
+
+			#if SPINE_TRIANGLECHECK
+			this.hasActiveClipping = other.hasActiveClipping;
+			this.rawVertexCount = other.rawVertexCount;
+			this.attachments.Clear(false);
+			this.attachments.GrowIfNeeded(other.attachments.Capacity);
+			this.attachments.Count = other.attachments.Count;
+			other.attachments.CopyTo(this.attachments.Items);
+			#endif
+
+			this.submeshInstructions.Clear(false);
+			this.submeshInstructions.GrowIfNeeded(other.submeshInstructions.Capacity);
+			this.submeshInstructions.Count = other.submeshInstructions.Count;
+			other.submeshInstructions.CopyTo(this.submeshInstructions.Items);
+		}
+
+		public static bool GeometryNotEqual (SkeletonRendererInstruction a, SkeletonRendererInstruction b) {
+			#if SPINE_TRIANGLECHECK
+			#if UNITY_EDITOR
+			if (!Application.isPlaying)
+			return true;
+			#endif
+
+			if (a.hasActiveClipping || b.hasActiveClipping) return true; // Triangles are unpredictable when clipping is active.
+
+			// Everything below assumes the raw vertex and triangle counts were used. (ie, no clipping was done)
+			if (a.rawVertexCount != b.rawVertexCount) return true;
+
+			if (a.immutableTriangles != b.immutableTriangles) return true;
+
+			int attachmentCountB = b.attachments.Count;
+			if (a.attachments.Count != attachmentCountB) return true; // Bounds check for the looped storedAttachments count below.
+
+			// Submesh count changed
+			int submeshCountA = a.submeshInstructions.Count;
+			int submeshCountB = b.submeshInstructions.Count;
+			if (submeshCountA != submeshCountB) return true;
+
+			// Submesh Instruction mismatch
+			var submeshInstructionsItemsA = a.submeshInstructions.Items;
+			var submeshInstructionsItemsB = b.submeshInstructions.Items;
+
+			var attachmentsA = a.attachments.Items;
+			var attachmentsB = b.attachments.Items;		
+			for (int i = 0; i < attachmentCountB; i++)
+				if (!System.Object.ReferenceEquals(attachmentsA[i], attachmentsB[i])) return true;
+			
+			for (int i = 0; i < submeshCountB; i++) {
+				var submeshA = submeshInstructionsItemsA[i];
+				var submeshB = submeshInstructionsItemsB[i];
+
+				if (!(
+					submeshA.rawVertexCount == submeshB.rawVertexCount &&
+					submeshA.startSlot == submeshB.startSlot &&
+					submeshA.endSlot == submeshB.endSlot
+					&& submeshA.rawTriangleCount == submeshB.rawTriangleCount &&
+					submeshA.rawFirstVertexIndex == submeshB.rawFirstVertexIndex
+				))
+					return true;			
+			}
+
+			return false;
+			#else
+			// In normal immutable triangle use, immutableTriangles will be initially false, forcing the smartmesh to update the first time but never again after that, unless there was an immutableTriangles flag mismatch..
+			if (a.immutableTriangles || b.immutableTriangles)
+				return (a.immutableTriangles != b.immutableTriangles);
+
+			return true;
+			#endif
+		}
+	}
+
 }

+ 7 - 2
spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs

@@ -41,6 +41,7 @@ namespace Spine.Unity.Editor {
 		SerializedProperty material_, color_;
 		SerializedProperty skeletonDataAsset_, initialSkinName_;
 		SerializedProperty startingAnimation_, startingLoop_, timeScale_, freeze_, unscaledTime_, tintBlack_;
+		SerializedProperty meshGeneratorSettings_;
 		SerializedProperty raycastTarget_;
 
 		SkeletonGraphic thisSkeletonGraphic;
@@ -65,6 +66,8 @@ namespace Spine.Unity.Editor {
 			timeScale_ = so.FindProperty("timeScale");
 			unscaledTime_ = so.FindProperty("unscaledTime");
 			freeze_ = so.FindProperty("freeze");
+
+			meshGeneratorSettings_ = so.FindProperty("meshGenerator").FindPropertyRelative("settings");
 		}
 
 		public override void OnInspectorGUI () {
@@ -80,10 +83,12 @@ namespace Spine.Unity.Editor {
 				serializedObject.Update();
 				return;
 			}
+			using (new SpineInspectorUtility.BoxScope()) {
+				EditorGUILayout.PropertyField(meshGeneratorSettings_, new GUIContent("Advanced..."), includeChildren: true);
+			}
 
 			EditorGUILayout.Space();
 			EditorGUILayout.PropertyField(initialSkinName_);
-			//EditorGUILayout.PropertyField(tintBlack_);
 			EditorGUILayout.Space();
 			EditorGUILayout.LabelField("Animation", EditorStyles.boldLabel);
 			EditorGUILayout.PropertyField(startingAnimation_);
@@ -106,7 +111,7 @@ namespace Spine.Unity.Editor {
 		[MenuItem("CONTEXT/SkeletonGraphic/Match RectTransform with Mesh Bounds")]
 		static void MatchRectTransformWithBounds (MenuCommand command) {
 			var skeletonGraphic = (SkeletonGraphic)command.context;
-			var mesh = skeletonGraphic.SpineMeshGenerator.LastGeneratedMesh;
+			var mesh = skeletonGraphic.GetComponent<MeshFilter>().sharedMesh;
 
 			mesh.RecalculateBounds();
 			var bounds = mesh.bounds;

+ 26 - 13
spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs

@@ -150,9 +150,10 @@ namespace Spine.Unity {
 		protected Spine.AnimationState state;
 		public Spine.AnimationState AnimationState { get { return state; } }
 
-		// This is any object that can give you a mesh when you give it a skeleton to render.
-		protected Spine.Unity.MeshGeneration.ISimpleMeshGenerator spineMeshGenerator;
-		public Spine.Unity.MeshGeneration.ISimpleMeshGenerator SpineMeshGenerator { get { return this.spineMeshGenerator; } }
+		[SerializeField] protected Spine.Unity.MeshGenerator meshGenerator = new MeshGenerator();
+		public Spine.Unity.MeshGenerator MeshGenerator { get { return this.meshGenerator; } }
+		DoubleBuffered<Spine.Unity.MeshRendererBuffers.SmartMesh> meshBuffers;
+		SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
 
 		public event UpdateBonesDelegate UpdateLocal;
 		public event UpdateBonesDelegate UpdateWorld;
@@ -180,9 +181,7 @@ namespace Spine.Unity {
 			}
 
 			this.skeleton = new Skeleton(skeletonData);
-			this.spineMeshGenerator = new Spine.Unity.MeshGeneration.ArraysSimpleMeshGenerator(); // You can switch this out with any other implementer of Spine.Unity.MeshGeneration.ISimpleMeshGenerator
-			//this.spineMeshGenerator.AddBlackTint = this.tintBlack;
-			this.spineMeshGenerator.PremultiplyVertexColors = true;
+			meshBuffers = new DoubleBuffered<MeshRendererBuffers.SmartMesh>();
 
 			// Set the initial Skin and Animation
 			if (!string.IsNullOrEmpty(initialSkinName))
@@ -193,14 +192,28 @@ namespace Spine.Unity {
 		}
 
 		public void UpdateMesh () {
-			if (this.IsValid) {
-				skeleton.SetColor(this.color);
-				if (canvas != null)
-					spineMeshGenerator.Scale = canvas.referencePixelsPerUnit; //JOHN: left a todo: move this to a listener to of the canvas?
+			if (!this.IsValid) return;
 
-				canvasRenderer.SetMesh(spineMeshGenerator.GenerateMesh(skeleton));
-				//this.UpdateMaterial(); // TODO: This allocates memory.
-			}
+			skeleton.SetColor(this.color);
+
+			var currentInstructions = this.currentInstructions;
+			MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, true, this.material);
+			meshGenerator.BeginNewMesh();
+			meshGenerator.AddSubmesh(currentInstructions.submeshInstructions.Items[0]);
+			if (canvas != null)
+				meshGenerator.ScaleVertexData(canvas.referencePixelsPerUnit);
+
+			var smartMesh = meshBuffers.GetNext();
+			var mesh = smartMesh.mesh;
+
+			meshGenerator.FillVertexData(mesh);
+			if (SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed))
+				meshGenerator.FillTrianglesSingle(mesh);
+			
+			canvasRenderer.SetMesh(mesh);
+			smartMesh.instructionUsed.Set(currentInstructions);
+
+			//this.UpdateMaterial(); // TODO: This allocates memory.
 		}
 		#endregion
 	}

+ 41 - 10
spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs

@@ -29,15 +29,14 @@
  *****************************************************************************/
 
 using UnityEngine;
-using Spine.Unity.MeshGeneration;
 
 namespace Spine.Unity.Modules {
 	[RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
 	public class SkeletonPartsRenderer : MonoBehaviour {
 
 		#region Properties
-		ISubmeshSetMeshGenerator meshGenerator;
-		public ISubmeshSetMeshGenerator MeshGenerator {
+		MeshGenerator meshGenerator;
+		public MeshGenerator MeshGenerator {
 			get {
 				LazyIntialize();
 				return meshGenerator;
@@ -61,11 +60,21 @@ namespace Spine.Unity.Modules {
 		}
 		#endregion
 
+		MeshRendererBuffers buffers;
+		SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
+
+
 		void LazyIntialize () {
-			if (meshGenerator != null) return;
-			meshGenerator = new ArraysSubmeshSetMeshGenerator();
-			meshFilter = GetComponent<MeshFilter>();
-			meshRenderer = GetComponent<MeshRenderer>();
+			if (buffers == null) {
+				buffers = new MeshRendererBuffers();
+				buffers.Initialize();
+
+				if (meshGenerator != null) return;
+				meshGenerator = new MeshGenerator();
+				meshFilter = GetComponent<MeshFilter>();
+				meshRenderer = GetComponent<MeshRenderer>();
+				currentInstructions.Clear();
+			}
 		}
 
 		public void ClearMesh () {
@@ -75,9 +84,31 @@ namespace Spine.Unity.Modules {
 
 		public void RenderParts (ExposedList<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh) {
 			LazyIntialize();
-			MeshAndMaterials m = meshGenerator.GenerateMesh(instructions, startSubmesh, endSubmesh);
-			meshFilter.sharedMesh = m.mesh;
-			meshRenderer.sharedMaterials = m.materials;
+
+			// STEP 1: Create instruction
+			currentInstructions.SetWithSubset(instructions, startSubmesh, endSubmesh);
+
+			// STEP 2: Generate mesh buffers.
+			var currentInstructionsSubmeshesItems = currentInstructions.submeshInstructions.Items;
+			meshGenerator.BeginNewMesh();
+			for (int i = 0; i < currentInstructions.submeshInstructions.Count; i++)
+				meshGenerator.AddSubmesh(currentInstructionsSubmeshesItems[i]);
+
+			buffers.UpdateSharedMaterials(currentInstructions.submeshInstructions);
+
+			// STEP 3: modify mesh.
+			var smartMesh = buffers.GetNextMesh();
+			var mesh = smartMesh.mesh;
+			meshGenerator.FillVertexData(mesh);
+			if (SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed)) {
+				meshGenerator.FillTriangles(mesh);
+				meshRenderer.sharedMaterials = buffers.GetUpdatedShaderdMaterialsArray();
+			} else if (buffers.MaterialsChangedInLastUpdate()) {
+				meshRenderer.sharedMaterials = buffers.GetUpdatedShaderdMaterialsArray();
+			}
+
+			meshFilter.sharedMesh = mesh;
+			smartMesh.instructionUsed.Set(currentInstructions);
 		}
 
 		public void SetPropertyBlock (MaterialPropertyBlock block) {

+ 14 - 18
spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs

@@ -130,40 +130,36 @@ namespace Spine.Unity.Modules {
 
 		MaterialPropertyBlock copiedBlock;
 
-		void HandleRender (SkeletonRenderer.SmartMesh.Instruction instruction) {
+		void HandleRender (SkeletonRendererInstruction instruction) {
 			int rendererCount = partsRenderers.Count;
 			if (rendererCount <= 0) return;
 
 			if (copyPropertyBlock)
 				mainMeshRenderer.GetPropertyBlock(copiedBlock);
 
+			var settings = new MeshGenerator.Settings {
+				addNormals = skeletonRenderer.addNormals,
+				calculateTangents = skeletonRenderer.calculateTangents,
+				immutableTriangles = false, // parts cannot do immutable triangles.
+				pmaVertexColors = skeletonRenderer.pmaVertexColors,
+				renderMeshes = skeletonRenderer.renderMeshes,
+				tintBlack = skeletonRenderer.tintBlack,
+				useClipping = true,
+				zSpacing = skeletonRenderer.zSpacing
+			};
+
 			var submeshInstructions = instruction.submeshInstructions;
 			var submeshInstructionsItems = submeshInstructions.Items;
 			int lastSubmeshInstruction = submeshInstructions.Count - 1;
 
-			#if SPINE_OPTIONAL_NORMALS
-			bool addNormals = skeletonRenderer.calculateNormals;
-			#endif
-
-			#if SPINE_OPTIONAL_SOLVETANGENTS
-			bool addTangents = skeletonRenderer.calculateTangents;
-			#endif
-
-			bool pmaVertexColors = skeletonRenderer.pmaVertexColors;
-
 			int rendererIndex = 0;
 			var currentRenderer = partsRenderers[rendererIndex];
 			for (int si = 0, start = 0; si <= lastSubmeshInstruction; si++) {
 				if (submeshInstructionsItems[si].forceSeparate || si == lastSubmeshInstruction) {
 					// Apply properties
 					var meshGenerator = currentRenderer.MeshGenerator;
-					#if SPINE_OPTIONAL_NORMALS
-					meshGenerator.AddNormals = addNormals;
-					#endif
-					#if SPINE_OPTIONAL_SOLVETANGENTS
-					meshGenerator.AddTangents = addTangents;
-					#endif
-					meshGenerator.PremultiplyVertexColors = pmaVertexColors;
+					meshGenerator.settings = settings;
+
 					if (copyPropertyBlock)
 						currentRenderer.SetPropertyBlock(copiedBlock);
 

+ 56 - 466
spine-unity/Assets/spine-unity/SkeletonRenderer.cs

@@ -30,14 +30,9 @@
 
 #define SPINE_OPTIONAL_RENDEROVERRIDE
 #define SPINE_OPTIONAL_MATERIALOVERRIDE
-#define SPINE_OPTIONAL_NORMALS
-#define SPINE_OPTIONAL_SOLVETANGENTS
-//#define SPINE_OPTIONAL_FRONTFACING
 
-using System;
 using System.Collections.Generic;
 using UnityEngine;
-using Spine.Unity.MeshGeneration;
 
 namespace Spine.Unity {
 	/// <summary>Renders a skeleton.</summary>
@@ -54,30 +49,25 @@ namespace Spine.Unity {
 
 		#region Advanced
 		// Submesh Separation
-		[UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators")]
-		[SpineSlot]
-		public string[] separatorSlotNames = new string[0];
-		[System.NonSerialized]
-		public readonly List<Slot> separatorSlots = new List<Slot>();
-
-		public float zSpacing;
-		public bool renderMeshes = true, immutableTriangles;
+		[UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators")] [SpineSlot] public string[] separatorSlotNames = new string[0];
+		[System.NonSerialized] public readonly List<Slot> separatorSlots = new List<Slot>();
+
+		[Range(-0.1f, 0f)] public float zSpacing;
+		public bool renderMeshes = true;
+		public bool immutableTriangles = false;
 		public bool pmaVertexColors = true;
 		public bool clearStateOnDisable = false;
 		public bool tintBlack = false;
 
-		#if SPINE_OPTIONAL_NORMALS
-		public bool calculateNormals;
-		#endif
-		#if SPINE_OPTIONAL_SOLVETANGENTS
+		[UnityEngine.Serialization.FormerlySerializedAs("calculateNormals")]
+		public bool addNormals;
 		public bool calculateTangents;
-		#endif
 
 		public bool logErrors = false;
 
 		#if SPINE_OPTIONAL_RENDEROVERRIDE
 		public bool disableRenderingOnOverride = true;
-		public delegate void InstructionDelegate (SkeletonRenderer.SmartMesh.Instruction instruction);
+		public delegate void InstructionDelegate (SkeletonRendererInstruction instruction);
 		event InstructionDelegate generateMeshOverride;
 		public event InstructionDelegate GenerateMeshOverride {
 			add {
@@ -118,27 +108,10 @@ namespace Spine.Unity {
 				return skeleton;
 			}
 		}
-
-		Spine.Unity.DoubleBuffered<SkeletonRenderer.SmartMesh> doubleBufferedMesh;
-		readonly SmartMesh.Instruction currentInstructions = new SmartMesh.Instruction();
-		readonly ExposedList<ArraysMeshGenerator.SubmeshTriangleBuffer> submeshes = new ExposedList<ArraysMeshGenerator.SubmeshTriangleBuffer>();
-		readonly ExposedList<Material> submeshMaterials = new ExposedList<Material>();
-		Material[] sharedMaterials = new Material[0];
-		float[] tempVertices = new float[8];
-		Vector3[] vertices;
-		Color32[] colors;
-		Vector2[] uvs;
-
-		Vector2[] uv2;
-		Vector2[] uv3;
-
-		#if SPINE_OPTIONAL_NORMALS
-		Vector3[] normals;
-		#endif
-		#if SPINE_OPTIONAL_SOLVETANGENTS
-		Vector4[] tangents;
-		Vector2[] tempTanBuffer;
-		#endif
+			
+		[System.NonSerialized] readonly SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
+		[System.NonSerialized] readonly MeshGenerator meshGenerator = new MeshGenerator();
+		[System.NonSerialized] readonly MeshRendererBuffers rendererBuffers = new MeshRendererBuffers();
 
 		#region Runtime Instantiation
 		public static T NewSpineGameObject<T> (SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer {
@@ -167,10 +140,7 @@ namespace Spine.Unity {
 		}
 
 		void OnDestroy () {
-			if (doubleBufferedMesh == null) return;
-			doubleBufferedMesh.GetNext().Dispose();
-			doubleBufferedMesh.GetNext().Dispose();
-			doubleBufferedMesh = null;
+			rendererBuffers.Dispose();
 		}
 
 		protected virtual void ClearState () {
@@ -192,14 +162,9 @@ namespace Spine.Unity {
 				if (meshRenderer != null) meshRenderer.sharedMaterial = null;
 
 				currentInstructions.Clear();
-				vertices = null;
-				colors = null;
-				uvs = null;
-				sharedMaterials = new Material[0];
-				submeshMaterials.Clear();
-				submeshes.Clear();
+				rendererBuffers.Clear();
+				meshGenerator.BeginNewMesh();
 				skeleton = null;
-
 				valid = false;
 			}
 
@@ -216,8 +181,7 @@ namespace Spine.Unity {
 
 			meshFilter = GetComponent<MeshFilter>();
 			meshRenderer = GetComponent<MeshRenderer>();
-			doubleBufferedMesh = new DoubleBuffered<SmartMesh>();
-			vertices = new Vector3[0];
+			rendererBuffers.Initialize();
 
 			skeleton = new Skeleton(skeletonData);
 			if (!string.IsNullOrEmpty(initialSkinName) && initialSkinName != "default")
@@ -234,446 +198,72 @@ namespace Spine.Unity {
 		}
 
 		public virtual void LateUpdate () {
-			if (!valid)
-				return;
-
-			if (
-				(!meshRenderer.enabled)
-				#if SPINE_OPTIONAL_RENDEROVERRIDE
-				&& this.generateMeshOverride == null
-				#endif
-			)
-				return;
-			
+			if (!valid) return;
 
-			// STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. ============================================================
-			ExposedList<Slot> drawOrder = skeleton.drawOrder;
-			var drawOrderItems = drawOrder.Items;
-			int drawOrderCount = drawOrder.Count;
-			bool renderMeshes = this.renderMeshes;
-
-			// Clear last state of attachments and submeshes
-			var workingInstruction = this.currentInstructions;
-			var workingAttachments = workingInstruction.attachments;
-			workingAttachments.Clear(false);
-			workingAttachments.GrowIfNeeded(drawOrderCount);
-			workingAttachments.Count = drawOrderCount;
-			var workingAttachmentsItems = workingInstruction.attachments.Items;
-
-			var workingSubmeshInstructions = workingInstruction.submeshInstructions;	// Items array should not be cached. There is dynamic writing to this list.
-			workingSubmeshInstructions.Clear(false);
-
-			#if !SPINE_TK2D
-			bool isCustomSlotMaterialsPopulated = customSlotMaterials.Count > 0;
+			#if SPINE_OPTIONAL_RENDEROVERRIDE
+			bool doMeshOverride = generateMeshOverride != null;
+			if ((!meshRenderer.enabled)	&& !doMeshOverride) return;
+			#else
+			const bool doMeshOverride = false;
+			if (!meshRenderer.enabled) return;
 			#endif
 
-			bool hasSeparators = separatorSlots.Count > 0;
-			int vertexCount = 0;
-			int submeshVertexCount = 0;
-			int submeshTriangleCount = 0, submeshFirstVertex = 0, submeshStartSlotIndex = 0;
-			Material lastMaterial = null;
-			for (int i = 0; i < drawOrderCount; i++) {
-				Slot slot = drawOrderItems[i];
-				Attachment attachment = slot.attachment;
-				workingAttachmentsItems[i] = attachment;
-
-				object rendererObject = null; // An AtlasRegion in plain Spine-Unity. Spine-TK2D hooks into TK2D's system. eventual source of Material object.
-				int attachmentVertexCount, attachmentTriangleCount;
-				bool noRender = false;
-
-				var regionAttachment = attachment as RegionAttachment;
-				if (regionAttachment != null) {
-					rendererObject = regionAttachment.RendererObject;
-					attachmentVertexCount = 4;
-					attachmentTriangleCount = 6;
-				} else {
-					if (!renderMeshes) {
-						noRender = true;
-						attachmentVertexCount = 0;
-						attachmentTriangleCount = 0;
-						//continue;
-					} else {
-						var meshAttachment = attachment as MeshAttachment;
-						if (meshAttachment != null) {
-							rendererObject = meshAttachment.RendererObject;
-							attachmentVertexCount = meshAttachment.worldVerticesLength >> 1;
-							attachmentTriangleCount = meshAttachment.triangles.Length;
-						} else {
-							noRender = true;
-							attachmentVertexCount = 0;
-							attachmentTriangleCount = 0;
-							//continue;
-						}
-					}
-				}
-
-				// Create a new SubmeshInstruction when material changes. (or when forced to separate by a submeshSeparator)
-				// Slot with a separator/new material will become the starting slot of the next new instruction.
-				bool forceSeparate = (hasSeparators && separatorSlots.Contains(slot));
-				if (noRender) {
-					if (forceSeparate && vertexCount > 0
-						#if SPINE_OPTIONAL_RENDEROVERRIDE
-						&& this.generateMeshOverride != null
-						#endif
-					) {
-						workingSubmeshInstructions.Add(
-							new Spine.Unity.MeshGeneration.SubmeshInstruction {
-								skeleton = this.skeleton,
-								material = lastMaterial,
-								startSlot = submeshStartSlotIndex,
-								endSlot = i,
-								triangleCount = submeshTriangleCount,
-								firstVertexIndex = submeshFirstVertex,
-								vertexCount = submeshVertexCount,
-								forceSeparate = forceSeparate
-							}
-						);
-						submeshTriangleCount = 0;
-						submeshVertexCount = 0;
-						submeshFirstVertex = vertexCount;
-						submeshStartSlotIndex = i;
-					}
-				} else {
-					#if !SPINE_TK2D
-					Material material;
-					if (isCustomSlotMaterialsPopulated) {
-						if (!customSlotMaterials.TryGetValue(slot, out material))
-							material = (Material)((AtlasRegion)rendererObject).page.rendererObject;				
-					} else {
-						material = (Material)((AtlasRegion)rendererObject).page.rendererObject;
-					}
-					#else
-					Material material = (rendererObject.GetType() == typeof(Material)) ? (Material)rendererObject : (Material)((AtlasRegion)rendererObject).page.rendererObject;
-					#endif
-
-					if (vertexCount > 0 && (forceSeparate || lastMaterial.GetInstanceID() != material.GetInstanceID())) {
-						workingSubmeshInstructions.Add(
-							new Spine.Unity.MeshGeneration.SubmeshInstruction {
-								skeleton = this.skeleton,
-								material = lastMaterial,
-								startSlot = submeshStartSlotIndex,
-								endSlot = i,
-								triangleCount = submeshTriangleCount,
-								firstVertexIndex = submeshFirstVertex,
-								vertexCount = submeshVertexCount,
-								forceSeparate = forceSeparate
-							}
-						);
-						submeshTriangleCount = 0;
-						submeshVertexCount = 0;
-						submeshFirstVertex = vertexCount;
-						submeshStartSlotIndex = i;
-					}
-					// Update state for the next iteration.
-					lastMaterial = material;
-					submeshTriangleCount += attachmentTriangleCount;
-					vertexCount += attachmentVertexCount;
-					submeshVertexCount += attachmentVertexCount;
-				}
-			}
-
-			if (submeshVertexCount != 0) {
-				workingSubmeshInstructions.Add(
-					new Spine.Unity.MeshGeneration.SubmeshInstruction {
-						skeleton = this.skeleton,
-						material = lastMaterial,
-						startSlot = submeshStartSlotIndex,
-						endSlot = drawOrderCount,
-						triangleCount = submeshTriangleCount,
-						firstVertexIndex = submeshFirstVertex,
-						vertexCount = submeshVertexCount,
-						forceSeparate = false
-					}
-				);
-			}
+			var currentInstructions = this.currentInstructions;
 
-			workingInstruction.vertexCount = vertexCount;
-			workingInstruction.immutableTriangles = this.immutableTriangles;
+			// STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. ============================================================
+			MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, customSlotMaterials, separatorSlots, doMeshOverride, this.immutableTriangles, this.renderMeshes);
 
 
 			// STEP 1.9. Post-process workingInstructions. ============================================================
-
 			#if SPINE_OPTIONAL_MATERIALOVERRIDE
-			// Material overrides are done here so they can be applied per submesh instead of per slot
-			// but they will still be passed through the GenerateMeshOverride delegate,
-			// and will still go through the normal material match check step in STEP 3.
-			if (customMaterialOverride.Count > 0) { // isCustomMaterialOverridePopulated 
-				var workingSubmeshInstructionsItems = workingSubmeshInstructions.Items;
-				for (int i = 0; i < workingSubmeshInstructions.Count; i++) {
-					var m = workingSubmeshInstructionsItems[i].material;
-					Material mo;
-					if (customMaterialOverride.TryGetValue(m, out mo)) {
-						workingSubmeshInstructionsItems[i].material = mo;
-					}
-				}
-			}
+			if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated 
+				MeshGenerator.TryReplaceMaterials(currentInstructions.submeshInstructions, customMaterialOverride);
 			#endif
+
 			#if SPINE_OPTIONAL_RENDEROVERRIDE
-			if (this.generateMeshOverride != null) {
-				this.generateMeshOverride(workingInstruction);
+			if (doMeshOverride) {
+				this.generateMeshOverride(currentInstructions);
 				if (disableRenderingOnOverride) return;
 			}
 			#endif
 
 
 			// STEP 2. Update vertex buffer based on verts from the attachments.  ============================================================
-			// Uses values that were also stored in workingInstruction.
-			if (tintBlack) {
-				ArraysMeshGenerator.EnsureSize(vertexCount, ref this.uv2);
-				ArraysMeshGenerator.EnsureSize(vertexCount, ref this.uv3);
-			}
-
-			#if SPINE_OPTIONAL_NORMALS
-			bool vertexCountIncreased = ArraysMeshGenerator.EnsureSize(vertexCount, ref this.vertices, ref this.uvs, ref this.colors);
-			if (vertexCountIncreased && calculateNormals) {
-				Vector3[] localNormals = this.normals = new Vector3[vertexCount];
-				Vector3 normal = new Vector3(0, 0, -1);
-				for (int i = 0; i < vertexCount; i++)
-					localNormals[i] = normal;
-			}
-			#else
-			ArraysMeshGenerator.EnsureSize(vertexCount, ref this.vertices, ref this.uvs, ref this.colors);
-			#endif
-
-			Vector3 meshBoundsMin;
-			Vector3 meshBoundsMax;
-			if (vertexCount <= 0) {
-				meshBoundsMin = new Vector3(0, 0, 0);
-				meshBoundsMax = new Vector3(0, 0, 0);
-			} else {
-				meshBoundsMin.x = int.MaxValue;
-				meshBoundsMin.y = int.MaxValue;
-				meshBoundsMax.x = int.MinValue;
-				meshBoundsMax.y = int.MinValue;
-
-				if (zSpacing > 0f) {
-					meshBoundsMin.z = 0f;
-					meshBoundsMax.z = zSpacing * (drawOrderCount - 1);
-				} else {
-					meshBoundsMin.z = zSpacing * (drawOrderCount - 1);
-					meshBoundsMax.z = 0f;
-				}
+			meshGenerator.BeginNewMesh();
+			meshGenerator.settings = new MeshGenerator.Settings {
+				pmaVertexColors = this.pmaVertexColors,
+				zSpacing = this.zSpacing,
+				useClipping = true,
+				tintBlack = this.tintBlack,
+				calculateTangents = this.calculateTangents,
+				renderMeshes = this.renderMeshes,
+				addNormals = this.addNormals
+			};
+
+			var workingSubmeshInstructions = currentInstructions.submeshInstructions;
+			foreach (var submeshInstruction in workingSubmeshInstructions) {
+				meshGenerator.AddSubmesh(submeshInstruction);
 			}
-			int vertexIndex = 0;
-
-			if (tintBlack)
-				ArraysMeshGenerator.FillBlackUVs(skeleton, 0, drawOrderCount, this.uv2, this.uv3, vertexIndex, renderMeshes); // This needs to be called before FillVerts so we have the correct vertexIndex argument.
-
-			ArraysMeshGenerator.FillVerts(skeleton, 0, drawOrderCount, this.zSpacing, pmaVertexColors, this.vertices, this.uvs, this.colors, ref vertexIndex, ref tempVertices, ref meshBoundsMin, ref meshBoundsMax, renderMeshes);
-
-
+				
 
 			// Step 3. Move the mesh data into a UnityEngine.Mesh ============================================================
-			var currentSmartMesh = doubleBufferedMesh.GetNext();	// Double-buffer for performance.
+			var currentSmartMesh = rendererBuffers.GetNextMesh();	// Double-buffer for performance.
 			var currentMesh = currentSmartMesh.mesh;
-			currentMesh.vertices = this.vertices;
-			currentMesh.colors32 = colors;
-			currentMesh.uv = uvs;
-			currentMesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);
-
-			if (tintBlack) {
-				currentMesh.uv2 = this.uv2;
-				currentMesh.uv3 = this.uv3;
-			}
+			meshGenerator.FillVertexData(currentMesh);
 
-			var currentSmartMeshInstructionUsed = currentSmartMesh.instructionUsed;
-			#if SPINE_OPTIONAL_NORMALS
-			if (calculateNormals && currentSmartMeshInstructionUsed.vertexCount < vertexCount)
-				currentMesh.normals = normals;
-			#endif
+			rendererBuffers.UpdateSharedMaterials(workingSubmeshInstructions);
 
 			// Check if the triangles should also be updated.
-			// This thorough structure check is cheaper than updating triangles every frame.
-			bool mustUpdateMeshStructure = CheckIfMustUpdateMeshStructure(workingInstruction, currentSmartMeshInstructionUsed);
-			int submeshCount = workingSubmeshInstructions.Count;
-			if (mustUpdateMeshStructure) {
-				var thisSubmeshMaterials = this.submeshMaterials;
-				thisSubmeshMaterials.Clear(false);
-
-				int oldSubmeshCount = submeshes.Count;
-
-				if (submeshes.Capacity < submeshCount)
-					submeshes.Capacity = submeshCount;
-				for (int i = oldSubmeshCount; i < submeshCount; i++)
-					submeshes.Items[i] = new ArraysMeshGenerator.SubmeshTriangleBuffer(workingSubmeshInstructions.Items[i].triangleCount);
-				submeshes.Count = submeshCount;
-					
-				var mutableTriangles = !workingInstruction.immutableTriangles;
-				for (int i = 0, last = submeshCount - 1; i < submeshCount; i++) {
-					var submeshInstruction = workingSubmeshInstructions.Items[i];
-
-					if (mutableTriangles || i >= oldSubmeshCount) {
-
-						var currentSubmesh = submeshes.Items[i];
-						int instructionTriangleCount = submeshInstruction.triangleCount;
-						if (renderMeshes) {
-							ArraysMeshGenerator.FillTriangles(ref currentSubmesh.triangles, skeleton, instructionTriangleCount, submeshInstruction.firstVertexIndex, submeshInstruction.startSlot, submeshInstruction.endSlot, (i == last));
-							currentSubmesh.triangleCount = instructionTriangleCount;
-						} else {
-							ArraysMeshGenerator.FillTrianglesQuads(ref currentSubmesh.triangles, ref currentSubmesh.triangleCount, ref currentSubmesh.firstVertex, submeshInstruction.firstVertexIndex, instructionTriangleCount, (i == last));
-						}
-
-					}
-
-					thisSubmeshMaterials.Add(submeshInstruction.material);
-				}
-
-				currentMesh.subMeshCount = submeshCount;
-
-				for (int i = 0; i < submeshCount; ++i)
-					currentMesh.SetTriangles(submeshes.Items[i].triangles, i);
-			}
-
-			#if SPINE_OPTIONAL_SOLVETANGENTS
-			if (calculateTangents) {
-				ArraysMeshGenerator.SolveTangents2DEnsureSize(ref this.tangents, ref this.tempTanBuffer, vertices.Length);
-				for (int i = 0; i < submeshCount; i++) {
-					var submesh = submeshes.Items[i];
-					ArraysMeshGenerator.SolveTangents2DTriangles(this.tempTanBuffer, submesh.triangles, submesh.triangleCount, this.vertices, this.uvs, vertexCount);
-				}
-				ArraysMeshGenerator.SolveTangents2DBuffer(this.tangents, this.tempTanBuffer, vertexCount);
-				currentMesh.tangents = this.tangents;
-			}
-			#endif
-				
-			// CheckIfMustUpdateMaterialArray (last pushed materials vs currently parsed materials)
-			// Needs to check against the Working Submesh Instructions Materials instead of the cached submeshMaterials.
-			{
-				var lastPushedMaterials = this.sharedMaterials;
-				bool mustUpdateRendererMaterials = mustUpdateMeshStructure ||
-					(lastPushedMaterials.Length != submeshCount);
-
-				// Assumption at this point: (lastPushedMaterials.Count == workingSubmeshInstructions.Count == thisSubmeshMaterials.Count == submeshCount)
-
-				// Case: mesh structure or submesh count did not change but materials changed.
-				if (!mustUpdateRendererMaterials) {
-					var workingSubmeshInstructionsItems = workingSubmeshInstructions.Items;
-					for (int i = 0; i < submeshCount; i++) {
-						if (lastPushedMaterials[i].GetInstanceID() != workingSubmeshInstructionsItems[i].material.GetInstanceID()) {   // Bounds check is implied by submeshCount above.
-							mustUpdateRendererMaterials = true;
-							{
-								var thisSubmeshMaterials = this.submeshMaterials.Items;
-								if (mustUpdateRendererMaterials)
-									for (int j = 0; j < submeshCount; j++)
-										thisSubmeshMaterials[j] = workingSubmeshInstructionsItems[j].material;
-							}
-							break;
-						}
-					}
-				}
-
-				if (mustUpdateRendererMaterials) {
-					if (submeshMaterials.Count == sharedMaterials.Length)
-						submeshMaterials.CopyTo(sharedMaterials);
-					else
-						sharedMaterials = submeshMaterials.ToArray();
-
-					meshRenderer.sharedMaterials = sharedMaterials;
-				}
+			if (SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed)) { // This thorough structure check is cheaper than updating triangles every frame.
+				meshGenerator.FillTriangles(currentMesh);
+				meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedShaderdMaterialsArray();
+			} else if (rendererBuffers.MaterialsChangedInLastUpdate()) {
+				meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedShaderdMaterialsArray();
 			}
 
-
 			// Step 4. The UnityEngine.Mesh is ready. Set it as the MeshFilter's mesh. Store the instructions used for that mesh. ============================================================
 			meshFilter.sharedMesh = currentMesh;
-			currentSmartMesh.instructionUsed.Set(workingInstruction);
-
-		}
-
-		static bool CheckIfMustUpdateMeshStructure (SmartMesh.Instruction a, SmartMesh.Instruction b) {
-
-			#if UNITY_EDITOR
-			if (!Application.isPlaying)
-				return true;
-			#endif
-
-			if (a.vertexCount != b.vertexCount)
-				return true;
-
-			if (a.immutableTriangles != b.immutableTriangles)
-				return true;
-
-			int attachmentCountB = b.attachments.Count;
-			if (a.attachments.Count != attachmentCountB) // Bounds check for the looped storedAttachments count below.
-				return true;
-
-			var attachmentsA = a.attachments.Items;
-			var attachmentsB = b.attachments.Items;		
-			for (int i = 0; i < attachmentCountB; i++) {
-				if (attachmentsA[i] != attachmentsB[i])
-					return true;
-			}
-
-			// Submesh count changed
-			int submeshCountA = a.submeshInstructions.Count;
-			int submeshCountB = b.submeshInstructions.Count;
-			if (submeshCountA != submeshCountB)
-				return true;
-
-			// Submesh Instruction mismatch
-			var submeshInstructionsItemsA = a.submeshInstructions.Items;
-			var submeshInstructionsItemsB = b.submeshInstructions.Items;
-			for (int i = 0; i < submeshCountB; i++) {
-				var submeshA = submeshInstructionsItemsA[i];
-				var submeshB = submeshInstructionsItemsB[i];
-
-				if (!(
-					submeshA.vertexCount == submeshB.vertexCount &&
-					submeshA.startSlot == submeshB.startSlot &&
-					submeshA.endSlot == submeshB.endSlot &&
-					submeshA.triangleCount == submeshB.triangleCount &&
-					submeshA.firstVertexIndex == submeshB.firstVertexIndex
-				))
-					return true;			
-			}
-
-			return false;
-		}
-
-		///<summary>This is a Mesh that also stores the instructions SkeletonRenderer generated for it.</summary>
-		public class SmartMesh : System.IDisposable {
-			public Mesh mesh = Spine.Unity.SpineMesh.NewMesh();
-			public SmartMesh.Instruction instructionUsed = new SmartMesh.Instruction();		
-
-			public void Dispose () {
-				if (mesh != null) {
-					#if UNITY_EDITOR
-					if (Application.isEditor && !Application.isPlaying)
-						UnityEngine.Object.DestroyImmediate(mesh);
-					else
-						UnityEngine.Object.Destroy(mesh);
-					#else
-					UnityEngine.Object.Destroy(mesh);
-					#endif
-				}
-				mesh = null;
-			}
-
-			public class Instruction {
-				public bool immutableTriangles;
-				public int vertexCount = -1;
-				public readonly ExposedList<Attachment> attachments = new ExposedList<Attachment>();
-				public readonly ExposedList<Spine.Unity.MeshGeneration.SubmeshInstruction> submeshInstructions = new ExposedList<Spine.Unity.MeshGeneration.SubmeshInstruction>();
-
-				public void Clear () {
-					this.attachments.Clear(false);
-					this.submeshInstructions.Clear(false);
-				}
-
-				public void Set (Instruction other) {
-					this.immutableTriangles = other.immutableTriangles;
-					this.vertexCount = other.vertexCount;
-
-					this.attachments.Clear(false);
-					this.attachments.GrowIfNeeded(other.attachments.Capacity);
-					this.attachments.Count = other.attachments.Count;
-					other.attachments.CopyTo(this.attachments.Items);
-
-					this.submeshInstructions.Clear(false);
-					this.submeshInstructions.GrowIfNeeded(other.submeshInstructions.Capacity);
-					this.submeshInstructions.Count = other.submeshInstructions.Count;
-					other.submeshInstructions.CopyTo(this.submeshInstructions.Items);
-				}
-			}
+			currentSmartMesh.instructionUsed.Set(currentInstructions);
 		}
 	}
 }