/******************************************************************************
 * 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_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>
	[ExecuteInEditMode, RequireComponent(typeof(MeshFilter), typeof(MeshRenderer)), DisallowMultipleComponent]
	[HelpURL("http://esotericsoftware.com/spine-unity-documentation#Rendering")]
	public class SkeletonRenderer : MonoBehaviour, ISkeletonComponent {

		public delegate void SkeletonRendererDelegate (SkeletonRenderer skeletonRenderer);
		public SkeletonRendererDelegate OnRebuild;

		public SkeletonDataAsset skeletonDataAsset;
		public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } } // ISkeletonComponent
		public string initialSkinName;

		#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;
		public bool pmaVertexColors = true;
		public bool clearStateOnDisable = false;

		#if SPINE_OPTIONAL_NORMALS
		public bool calculateNormals;
		#endif
		#if SPINE_OPTIONAL_SOLVETANGENTS
		public bool calculateTangents;
		#endif
		#if SPINE_OPTIONAL_FRONTFACING
		public bool frontFacing;
		#endif

		public bool logErrors = false;

		#if SPINE_OPTIONAL_RENDEROVERRIDE
		public bool disableRenderingOnOverride = true;
		public delegate void InstructionDelegate (SkeletonRenderer.SmartMesh.Instruction instruction);
		event InstructionDelegate generateMeshOverride;
		public event InstructionDelegate GenerateMeshOverride {
			add {
				generateMeshOverride += value;
				if (disableRenderingOnOverride && generateMeshOverride != null) {
					Initialize(false);
					meshRenderer.enabled = false;
				}
			}
			remove {
				generateMeshOverride -= value;
				if (disableRenderingOnOverride && generateMeshOverride == null) {
					Initialize(false);
					meshRenderer.enabled = true;
				}
			}
		}
		#endif

		#if SPINE_OPTIONAL_MATERIALOVERRIDE
		[System.NonSerialized] readonly Dictionary<Material, Material> customMaterialOverride = new Dictionary<Material, Material>();
		public Dictionary<Material, Material> CustomMaterialOverride { get { return customMaterialOverride; } }
		#endif

		// Custom Slot Material
		[System.NonSerialized] readonly Dictionary<Slot, Material> customSlotMaterials = new Dictionary<Slot, Material>();
		public Dictionary<Slot, Material> CustomSlotMaterials { get { return customSlotMaterials; } }
		#endregion

		MeshRenderer meshRenderer;
		MeshFilter meshFilter;

		[System.NonSerialized] public bool valid;
		[System.NonSerialized] public Skeleton skeleton;
		public Skeleton Skeleton {
			get {
				Initialize(false);
				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;
		#if SPINE_OPTIONAL_NORMALS
		Vector3[] normals;
		#endif
		#if SPINE_OPTIONAL_SOLVETANGENTS
		Vector4[] tangents;
		Vector2[] tempTanBuffer;
		#endif

		#region Runtime Instantiation
		public static T NewSpineGameObject<T> (SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer {
			return SkeletonRenderer.AddSpineComponent<T>(new GameObject("New Spine GameObject"), skeletonDataAsset);
		}

		/// <summary>Add and prepare a Spine component that derives from SkeletonRenderer to a GameObject at runtime.</summary>
		/// <typeparam name="T">T should be SkeletonRenderer or any of its derived classes.</typeparam>
		public static T AddSpineComponent<T> (GameObject gameObject, SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer {
			var c = gameObject.AddComponent<T>();
			if (skeletonDataAsset != null) {
				c.skeletonDataAsset = skeletonDataAsset;
				c.Initialize(false);
			}
			return c;
		}
		#endregion

		public virtual void Awake () {
			Initialize(false);
		}

		void OnDisable () {
			if (clearStateOnDisable && valid)
				ClearState();
		}

		protected virtual void ClearState () {
			meshFilter.sharedMesh = null;
			currentInstructions.Clear();
			if (skeleton != null) skeleton.SetToSetupPose();
		}

		public virtual void Initialize (bool overwrite) {
			if (valid && !overwrite)
				return;

			// Clear
			{
				if (meshFilter != null)
					meshFilter.sharedMesh = null;

				meshRenderer = GetComponent<MeshRenderer>();
				if (meshRenderer != null) meshRenderer.sharedMaterial = null;

				currentInstructions.Clear();
				vertices = null;
				colors = null;
				uvs = null;
				sharedMaterials = new Material[0];
				submeshMaterials.Clear();
				submeshes.Clear();
				skeleton = null;

				valid = false;
			}

			if (!skeletonDataAsset) {
				if (logErrors)
					Debug.LogError("Missing SkeletonData asset.", this);

				return;
			}
			SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(false);
			if (skeletonData == null)
				return;
			valid = true;

			meshFilter = GetComponent<MeshFilter>();
			meshRenderer = GetComponent<MeshRenderer>();
			doubleBufferedMesh = new DoubleBuffered<SmartMesh>();
			vertices = new Vector3[0];

			skeleton = new Skeleton(skeletonData);
			if (!string.IsNullOrEmpty(initialSkinName) && initialSkinName != "default")
				skeleton.SetSkin(initialSkinName);

			separatorSlots.Clear();
			for (int i = 0; i < separatorSlotNames.Length; i++)
				separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));

			LateUpdate();

			if (OnRebuild != null)
				OnRebuild(this);
		}

		public virtual void LateUpdate () {
			if (!valid)
				return;

			if (
				(!meshRenderer.enabled)
				#if SPINE_OPTIONAL_RENDEROVERRIDE
				&& this.generateMeshOverride == null
				#endif
			)
				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;

			#if SPINE_OPTIONAL_FRONTFACING
			var workingFlips = workingInstruction.attachmentFlips;
			workingFlips.Clear(false);
			workingFlips.GrowIfNeeded(drawOrderCount);
			workingFlips.Count = drawOrderCount;
			var workingFlipsItems = workingFlips.Items;
			#endif

			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;
			#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;

				#if SPINE_OPTIONAL_FRONTFACING
				bool flip = frontFacing && (slot.bone.WorldSignX != slot.bone.WorldSignY);
				workingFlipsItems[i] = flip;
				#endif

				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 && this.generateMeshOverride != null) {
						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
					}
				);
			}

			workingInstruction.vertexCount = vertexCount;
			workingInstruction.immutableTriangles = this.immutableTriangles;
			#if SPINE_OPTIONAL_FRONTFACING
			workingInstruction.frontFacing = this.frontFacing;
			#endif


			// 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;
					}
				}
			}
			#endif
			#if SPINE_OPTIONAL_RENDEROVERRIDE
			if (this.generateMeshOverride != null) {
				this.generateMeshOverride(workingInstruction);
				if (disableRenderingOnOverride) return;
			}
			#endif


			// STEP 2. Update vertex buffer based on verts from the attachments.  ============================================================
			// Uses values that were also stored in workingInstruction.
			bool vertexCountIncreased = ArraysMeshGenerator.EnsureSize(vertexCount, ref this.vertices, ref this.uvs, ref this.colors);
			#if SPINE_OPTIONAL_NORMALS
			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;
			}
			#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;
				}
			}
			int vertexIndex = 0;
			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 currentMesh = currentSmartMesh.mesh;
			currentMesh.vertices = this.vertices;
			currentMesh.colors32 = colors;
			currentMesh.uv = uvs;
			currentMesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);

			var currentSmartMeshInstructionUsed = currentSmartMesh.instructionUsed;
			#if SPINE_OPTIONAL_NORMALS
			if (calculateNormals && currentSmartMeshInstructionUsed.vertexCount < vertexCount)
				currentMesh.normals = normals;
			#endif

			// 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) {
	
						#if !SPINE_OPTIONAL_FRONTFACING
						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));
						}
						#else
						SetSubmesh(i, submeshInstruction, currentInstructions.attachmentFlips, i == last);
						#endif

					}

					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;
				}
			}


			// 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;
			}

			#if SPINE_OPTIONAL_FRONTFACING
			if (a.frontFacing != b.frontFacing) { 	// if settings changed
				return true;
			} else if (a.frontFacing) { 			// if settings matched, only need to check one.
				var flipsA = a.attachmentFlips.Items;
				var flipsB = b.attachmentFlips.Items;
				for (int i = 0; i < attachmentCountB; i++) {
					if (flipsA[i] != flipsB[i])
						return true;
				}
			}
			#endif

			// 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;
		}

		#if SPINE_OPTIONAL_FRONTFACING
		void SetSubmesh (int submeshIndex, Spine.Unity.MeshGeneration.SubmeshInstruction submeshInstructions, ExposedList<bool> flipStates, bool isLastSubmesh) {
			var currentSubmesh = submeshes.Items[submeshIndex];
			int[] triangles = currentSubmesh.triangles;

			int triangleCount = submeshInstructions.triangleCount;
			int firstVertex = submeshInstructions.firstVertexIndex;

			int trianglesCapacity = triangles.Length;
			if (isLastSubmesh && trianglesCapacity > triangleCount) {
				// Last submesh may have more triangles than required, so zero triangles to the end.
				for (int i = triangleCount; i < trianglesCapacity; i++)
					triangles[i] = 0;

				currentSubmesh.triangleCount = triangleCount;

			} else if (trianglesCapacity != triangleCount) {
				// Reallocate triangles when not the exact size needed.
				currentSubmesh.triangles = triangles = new int[triangleCount];
				currentSubmesh.triangleCount = 0;
			}
				
			if (!this.renderMeshes && !this.frontFacing) {
				// Use stored triangles if possible.
				if (currentSubmesh.firstVertex != firstVertex || currentSubmesh.triangleCount < triangleCount) { //|| currentSubmesh.triangleCount == 0
					currentSubmesh.triangleCount = triangleCount;
					currentSubmesh.firstVertex = firstVertex;

					for (int i = 0; i < triangleCount; i += 6, firstVertex += 4) {
						triangles[i] = firstVertex;
						triangles[i + 1] = firstVertex + 2;
						triangles[i + 2] = firstVertex + 1;
						triangles[i + 3] = firstVertex + 2;
						triangles[i + 4] = firstVertex + 3;
						triangles[i + 5] = firstVertex + 1;
					}
				}
				return;
			}
				
			var flipStatesItems = flipStates.Items;

			// Iterate through all slots and store their triangles. 
			var drawOrderItems = skeleton.DrawOrder.Items;
			int triangleIndex = 0; // Modified by loop
			for (int i = submeshInstructions.startSlot, n = submeshInstructions.endSlot; i < n; i++) {			
				Attachment attachment = drawOrderItems[i].attachment;
				bool flip = frontFacing && flipStatesItems[i];

				// Add RegionAttachment triangles
				if (attachment is RegionAttachment) {
					if (!flip) {
						triangles[triangleIndex] = firstVertex;
						triangles[triangleIndex + 1] = firstVertex + 2;
						triangles[triangleIndex + 2] = firstVertex + 1;
						triangles[triangleIndex + 3] = firstVertex + 2;
						triangles[triangleIndex + 4] = firstVertex + 3;
						triangles[triangleIndex + 5] = firstVertex + 1;
					} else {
						triangles[triangleIndex] = firstVertex + 1;
						triangles[triangleIndex + 1] = firstVertex + 2;
						triangles[triangleIndex + 2] = firstVertex;
						triangles[triangleIndex + 3] = firstVertex + 1;
						triangles[triangleIndex + 4] = firstVertex + 3;
						triangles[triangleIndex + 5] = firstVertex + 2;
					}

					triangleIndex += 6;
					firstVertex += 4;
					continue;
				}

				// Add (Weighted)MeshAttachment triangles
				int[] attachmentTriangles;
				int attachmentVertexCount;
				var meshAttachment = attachment as MeshAttachment;
				if (meshAttachment != null) {
					attachmentVertexCount = meshAttachment.worldVerticesLength >> 1; // length/2
					attachmentTriangles = meshAttachment.triangles;
				} else {
					continue;
				}

				if (flip) {
					for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii += 3, triangleIndex += 3) {
						triangles[triangleIndex + 2] = firstVertex + attachmentTriangles[ii];
						triangles[triangleIndex + 1] = firstVertex + attachmentTriangles[ii + 1];
						triangles[triangleIndex] = firstVertex + attachmentTriangles[ii + 2];
					}
				} else {
					for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, triangleIndex++) {
						triangles[triangleIndex] = firstVertex + attachmentTriangles[ii];
					}
				}

				firstVertex += attachmentVertexCount;
			}
		}
		#endif

		///<summary>This is a Mesh that also stores the instructions SkeletonRenderer generated for it.</summary>
		public class SmartMesh {
			public Mesh mesh = Spine.Unity.SpineMesh.NewMesh();
			public SmartMesh.Instruction instructionUsed = new SmartMesh.Instruction();		

			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>();

				#if SPINE_OPTIONAL_FRONTFACING
				public bool frontFacing;
				public readonly ExposedList<bool> attachmentFlips = new ExposedList<bool>();
				#endif

				public void Clear () {
					this.attachments.Clear(false);
					this.submeshInstructions.Clear(false);

					#if SPINE_OPTIONAL_FRONTFACING
					this.attachmentFlips.Clear(false);
					#endif
				}

				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);

					#if SPINE_OPTIONAL_FRONTFACING
					this.frontFacing = other.frontFacing;
					this.attachmentFlips.Clear(false);
					this.attachmentFlips.GrowIfNeeded(other.attachmentFlips.Capacity);
					this.attachmentFlips.Count = other.attachmentFlips.Count;
					if (this.frontFacing)
						other.attachmentFlips.CopyTo(this.attachmentFlips.Items);
					#endif

					this.submeshInstructions.Clear(false);
					this.submeshInstructions.GrowIfNeeded(other.submeshInstructions.Capacity);
					this.submeshInstructions.Count = other.submeshInstructions.Count;
					other.submeshInstructions.CopyTo(this.submeshInstructions.Items);
				}
			}
		}
	}
}
