/******************************************************************************
 * Spine Runtimes Software License
 * Version 2.3
 * 
 * Copyright (c) 2013-2015, 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 (the "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 otherwise create derivative works, improvements of the
 * Software or develop new applications using the Software 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; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) 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
#define SPINE_OPTIONAL_FRONTFACING
#define SPINE_OPTIONAL_RENDEROVERRIDE
#define SPINE_OPTIONAL_MATERIALOVERRIDE
//#define SPINE_OPTIONAL_SUBMESHRENDERER // Deprecated

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 {

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

		public SkeletonDataAsset skeletonDataAsset;
		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;
		#if SPINE_OPTIONAL_NORMALS
		public bool calculateNormals, 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_SUBMESHRENDERER
		private Spine.Unity.Modules.SkeletonUtilitySubmeshRenderer[] submeshRenderers;
		#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;

		Spine.Unity.DoubleBuffered<SkeletonRenderer.SmartMesh> doubleBufferedMesh;
		readonly SmartMesh.Instruction currentInstructions = new SmartMesh.Instruction();
		readonly ExposedList<SubmeshTriangleBuffer> submeshes = new ExposedList<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;
		Vector4[] tangents;
		#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);
		}

		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 (initialSkinName != null && initialSkinName.Length > 0 && initialSkinName != "default")
				skeleton.SetSkin(initialSkinName);

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

			#if SPINE_OPTIONAL_SUBMESHRENDERER
			submeshRenderers = GetComponentsInChildren<Spine.Unity.Modules.SkeletonUtilitySubmeshRenderer>();
			#endif

			LateUpdate();

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

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

			if (
				(
					!meshRenderer.enabled

				)
				#if SPINE_OPTIONAL_RENDEROVERRIDE
				&& this.generateMeshOverride == null
				#endif

				#if SPINE_OPTIONAL_SUBMESHRENDERER
				&& submeshRenderers.Length > 0
				#endif

			)
				return;



			// STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes.

			// This method caches several .Items arrays.
			// Never mutate their overlying ExposedList objects.
			ExposedList<Slot> drawOrder = skeleton.drawOrder;
			var drawOrderItems = drawOrder.Items;
			int drawOrderCount = drawOrder.Count;
			int separatorSlotCount = separatorSlots.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);

			bool isCustomSlotMaterialsPopulated = customSlotMaterials.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; // An AtlasRegion in plain Spine-Unity. Spine-TK2D hooks into TK2D's system. eventual source of Material object.
				int attachmentVertexCount, attachmentTriangleCount;

				var regionAttachment = attachment as RegionAttachment;
				if (regionAttachment != null) {
					rendererObject = regionAttachment.RendererObject;
					attachmentVertexCount = 4;
					attachmentTriangleCount = 6;
				} else {
					if (!renderMeshes)
						continue;
					var meshAttachment = attachment as MeshAttachment;
					if (meshAttachment != null) {
						rendererObject = meshAttachment.RendererObject;
						attachmentVertexCount = meshAttachment.vertices.Length >> 1;
						attachmentTriangleCount = meshAttachment.triangles.Length;
					} else {
						var skinnedMeshAttachment = attachment as WeightedMeshAttachment;
						if (skinnedMeshAttachment != null) {
							rendererObject = skinnedMeshAttachment.RendererObject;
							attachmentVertexCount = skinnedMeshAttachment.uvs.Length >> 1;
							attachmentTriangleCount = skinnedMeshAttachment.triangles.Length;
						} else
							continue;
					}
				}

				#if !SPINE_TK2D
				// Material material = (Material)((AtlasRegion)rendererObject).page.rendererObject; // For no customSlotMaterials

				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

				// Create a new SubmeshInstruction when material changes. (or when forced to separate by a submeshSeparator)
				bool forceSeparate = (separatorSlotCount > 0 && separatorSlots.Contains(slot));
				if ((vertexCount > 0 && lastMaterial.GetInstanceID() != material.GetInstanceID()) || forceSeparate) {
					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;
				}
				lastMaterial = material;

				submeshTriangleCount += attachmentTriangleCount;
				vertexCount += attachmentVertexCount;
				submeshVertexCount += attachmentVertexCount;
			}

			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.
			Vector3[] vertices = this.vertices;
			bool vertexCountIncreased = vertexCount > vertices.Length;	

			if (vertexCountIncreased) {
				this.vertices = vertices = new Vector3[vertexCount];
				this.colors = new Color32[vertexCount];
				this.uvs = new Vector2[vertexCount];

				#if SPINE_OPTIONAL_NORMALS
				if (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;
				}

				// For dynamic tangent calculation, you can remove the tangent-filling logic and add tangent calculation logic below.
				if (calculateTangents) {
					Vector4[] localTangents = this.tangents = new Vector4[vertexCount];
					Vector4 tangent = new Vector4(1, 0, 0, -1);
					for (int i = 0; i < vertexCount; i++)
						localTangents[i] = tangent;
				}
				#endif
			} else {
				Vector3 zero = Vector3.zero;
				for (int i = vertexCount, n = vertices.Length; i < n; i++)
					vertices[i] = zero;
			}

			float zSpacing = this.zSpacing;
			float[] tempVertices = this.tempVertices;
			Vector2[] uvs = this.uvs;
			Color32[] colors = this.colors;
			int vertexIndex = 0;
			bool pmaVertexColors = this.pmaVertexColors;
			Color32 color;
			float a = skeleton.a * 255, r = skeleton.r, g = skeleton.g, b = skeleton.b;

			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 i = 0;
				do {
					Slot slot = drawOrderItems[i];
					Attachment attachment = slot.attachment;
					RegionAttachment regionAttachment = attachment as RegionAttachment;
					if (regionAttachment != null) {
						regionAttachment.ComputeWorldVertices(slot.bone, tempVertices);

						float z = i * zSpacing;
						float x1 = tempVertices[RegionAttachment.X1], y1 = tempVertices[RegionAttachment.Y1];
						float x2 = tempVertices[RegionAttachment.X2], y2 = tempVertices[RegionAttachment.Y2];
						float x3 = tempVertices[RegionAttachment.X3], y3 = tempVertices[RegionAttachment.Y3];
						float x4 = tempVertices[RegionAttachment.X4], y4 = tempVertices[RegionAttachment.Y4];
						vertices[vertexIndex].x = x1;
						vertices[vertexIndex].y = y1;
						vertices[vertexIndex].z = z;
						vertices[vertexIndex + 1].x = x4;
						vertices[vertexIndex + 1].y = y4;
						vertices[vertexIndex + 1].z = z;
						vertices[vertexIndex + 2].x = x2;
						vertices[vertexIndex + 2].y = y2;
						vertices[vertexIndex + 2].z = z;
						vertices[vertexIndex + 3].x = x3;
						vertices[vertexIndex + 3].y = y3;
						vertices[vertexIndex + 3].z = z;

						if (pmaVertexColors) {
							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[vertexIndex] = color;
						colors[vertexIndex + 1] = color;
						colors[vertexIndex + 2] = color;
						colors[vertexIndex + 3] = color;

						float[] regionUVs = regionAttachment.uvs;
						uvs[vertexIndex].x = regionUVs[RegionAttachment.X1];
						uvs[vertexIndex].y = regionUVs[RegionAttachment.Y1];
						uvs[vertexIndex + 1].x = regionUVs[RegionAttachment.X4];
						uvs[vertexIndex + 1].y = regionUVs[RegionAttachment.Y4];
						uvs[vertexIndex + 2].x = regionUVs[RegionAttachment.X2];
						uvs[vertexIndex + 2].y = regionUVs[RegionAttachment.Y2];
						uvs[vertexIndex + 3].x = regionUVs[RegionAttachment.X3];
						uvs[vertexIndex + 3].y = regionUVs[RegionAttachment.Y3];

						// Calculate min/max X
						if (x1 < meshBoundsMin.x)
							meshBoundsMin.x = x1;
						else if (x1 > meshBoundsMax.x)
							meshBoundsMax.x = x1;
						if (x2 < meshBoundsMin.x)
							meshBoundsMin.x = x2;
						else if (x2 > meshBoundsMax.x)
							meshBoundsMax.x = x2;
						if (x3 < meshBoundsMin.x)
							meshBoundsMin.x = x3;
						else if (x3 > meshBoundsMax.x)
							meshBoundsMax.x = x3;
						if (x4 < meshBoundsMin.x)
							meshBoundsMin.x = x4;
						else if (x4 > meshBoundsMax.x)
							meshBoundsMax.x = x4;

						// Calculate min/max Y
						if (y1 < meshBoundsMin.y)
							meshBoundsMin.y = y1;
						else if (y1 > meshBoundsMax.y)
							meshBoundsMax.y = y1;
						if (y2 < meshBoundsMin.y)
							meshBoundsMin.y = y2;
						else if (y2 > meshBoundsMax.y)
							meshBoundsMax.y = y2;
						if (y3 < meshBoundsMin.y)
							meshBoundsMin.y = y3;
						else if (y3 > meshBoundsMax.y)
							meshBoundsMax.y = y3;
						if (y4 < meshBoundsMin.y)
							meshBoundsMin.y = y4;
						else if (y4 > meshBoundsMax.y)
							meshBoundsMax.y = y4;

						vertexIndex += 4;
					} else {
						if (!renderMeshes)
							continue;
						MeshAttachment meshAttachment = attachment as MeshAttachment;
						if (meshAttachment != null) {
							int meshVertexCount = meshAttachment.vertices.Length;
							if (tempVertices.Length < meshVertexCount)
								this.tempVertices = tempVertices = new float[meshVertexCount];
							meshAttachment.ComputeWorldVertices(slot, tempVertices);

							if (pmaVertexColors) {
								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[] meshUVs = meshAttachment.uvs;
							float z = i * zSpacing;
							for (int ii = 0; ii < meshVertexCount; ii += 2, vertexIndex++) {
								float x = tempVertices[ii], y = tempVertices[ii + 1];
								vertices[vertexIndex].x = x;
								vertices[vertexIndex].y = y;
								vertices[vertexIndex].z = z;
								colors[vertexIndex] = color;
								uvs[vertexIndex].x = meshUVs[ii];
								uvs[vertexIndex].y = meshUVs[ii + 1];

								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;
							}
						} else {
							WeightedMeshAttachment weightedMeshAttachment = attachment as WeightedMeshAttachment;
							if (weightedMeshAttachment != null) {
								int meshVertexCount = weightedMeshAttachment.uvs.Length;
								if (tempVertices.Length < meshVertexCount)
									this.tempVertices = tempVertices = new float[meshVertexCount];
								weightedMeshAttachment.ComputeWorldVertices(slot, tempVertices);

								if (pmaVertexColors) {
									color.a = (byte)(a * slot.a * weightedMeshAttachment.a);
									color.r = (byte)(r * slot.r * weightedMeshAttachment.r * color.a);
									color.g = (byte)(g * slot.g * weightedMeshAttachment.g * color.a);
									color.b = (byte)(b * slot.b * weightedMeshAttachment.b * color.a);
									if (slot.data.blendMode == BlendMode.additive) color.a = 0;
								} else {
									color.a = (byte)(a * slot.a * weightedMeshAttachment.a);
									color.r = (byte)(r * slot.r * weightedMeshAttachment.r * 255);
									color.g = (byte)(g * slot.g * weightedMeshAttachment.g * 255);
									color.b = (byte)(b * slot.b * weightedMeshAttachment.b * 255);
								}

								float[] meshUVs = weightedMeshAttachment.uvs;
								float z = i * zSpacing;
								for (int ii = 0; ii < meshVertexCount; ii += 2, vertexIndex++) {
									float x = tempVertices[ii], y = tempVertices[ii + 1];
									vertices[vertexIndex].x = x;
									vertices[vertexIndex].y = y;
									vertices[vertexIndex].z = z;
									colors[vertexIndex] = color;
									uvs[vertexIndex].x = meshUVs[ii];
									uvs[vertexIndex].y = meshUVs[ii + 1];

									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;
								}
							}
						}
					}
				} while (++i < drawOrderCount);
			}

			// Step 3. Move the mesh data into a UnityEngine.Mesh
			var currentSmartMesh = doubleBufferedMesh.GetNext();	// Double-buffer for performance.
			var currentMesh = currentSmartMesh.mesh;

			currentMesh.vertices = vertices;
			currentMesh.colors32 = colors;
			currentMesh.uv = uvs;

			Vector3 meshBoundsExtents = meshBoundsMax - meshBoundsMin;
			Vector3 meshBoundsCenter = meshBoundsMin + meshBoundsExtents * 0.5f;
			currentMesh.bounds = new Bounds(meshBoundsCenter, meshBoundsExtents);

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

				// For dynamic calculated tangents, this needs to be moved out of the vertexCount check block when replacing the logic, also ensuring the size.
				if (calculateTangents)
					currentMesh.tangents = this.tangents;
			}
			#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);
			if (mustUpdateMeshStructure) {
				var thisSubmeshMaterials = this.submeshMaterials;
				thisSubmeshMaterials.Clear(false);

				int submeshCount = workingSubmeshInstructions.Count;
				int oldSubmeshCount = submeshes.Count;

				submeshes.Capacity = submeshCount;
				for (int i = oldSubmeshCount; i < submeshCount; i++)
					submeshes.Items[i] = new SubmeshTriangleBuffer();

				var mutableTriangles = !workingInstruction.immutableTriangles;
				for (int i = 0, last = submeshCount - 1; i < submeshCount; i++) {
					var submeshInstruction = workingSubmeshInstructions.Items[i];
					if (mutableTriangles || i >= oldSubmeshCount)
						SetSubmesh(i, submeshInstruction,
							#if SPINE_OPTIONAL_FRONTFACING
							currentInstructions.attachmentFlips,
							#endif
							i == last);
					thisSubmeshMaterials.Add(submeshInstruction.material);
				}

				currentMesh.subMeshCount = submeshCount;

				for (int i = 0; i < submeshCount; ++i)
					currentMesh.SetTriangles(submeshes.Items[i].triangles, i);
			}

			// 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 != workingSubmeshInstructions.Count);

				if (!mustUpdateRendererMaterials) {
					var workingSubmeshInstructionsItems = workingSubmeshInstructions.Items;
					for (int i = 0, n = lastPushedMaterials.Length; i < n; i++) {
						if (lastPushedMaterials[i].GetInstanceID() != workingSubmeshInstructionsItems[i].material.GetInstanceID()) {   // Bounds check is implied above.
							mustUpdateRendererMaterials = true;
							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);


			// Step 5. Miscellaneous
			// Add stuff here if you want

			#if SPINE_OPTIONAL_SUBMESHRENDERER
			if (submeshRenderers.Length > 0) {
				for (int i = 0; i < submeshRenderers.Length; i++) {
					var submeshRenderer = submeshRenderers[i];
					if (submeshRenderer.submeshIndex < sharedMaterials.Length)
						submeshRenderer.SetMesh(meshRenderer, currentMesh, sharedMaterials[submeshRenderer.submeshIndex]);
					else
						submeshRenderer.GetComponent<Renderer>().enabled = false;
				}
			}
			#endif
		}

		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) {
		#else
		void SetSubmesh (int submeshIndex, Spine.Unity.MeshGeneration.SubmeshInstruction submeshInstructions, bool isLastSubmesh) {
		#endif
			SubmeshTriangleBuffer 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 SPINE_OPTIONAL_FRONTFACING
			if (!this.renderMeshes && !this.frontFacing) {
			#else
			if (!this.renderMeshes) {
			#endif
				// 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;
			}

			// This method caches several .Items arrays.
			// Never mutate their overlying ExposedList objects.

			#if SPINE_OPTIONAL_FRONTFACING
			var flipStatesItems = flipStates.Items;
			#endif

			// 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;
				#if SPINE_OPTIONAL_FRONTFACING
				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;
				}
				#else
				if (attachment is RegionAttachment) {
				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;

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

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

				#if SPINE_OPTIONAL_FRONTFACING
				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];
					}
				}
				#else
				for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, triangleIndex++) {
				triangles[triangleIndex] = firstVertex + attachmentTriangles[ii];
				}
				#endif

				firstVertex += attachmentVertexCount;
			}
		}

		#if UNITY_EDITOR
		void OnDrawGizmos () {
			// Make scene view selection easier by drawing a clear gizmo over the skeleton.
			meshFilter = GetComponent<MeshFilter>();
			if (meshFilter == null) return;

			Mesh mesh = meshFilter.sharedMesh;
			if (mesh == null) return;

			Bounds meshBounds = mesh.bounds;
			Gizmos.color = Color.clear;
			Gizmos.matrix = transform.localToWorldMatrix;
			Gizmos.DrawCube(meshBounds.center, meshBounds.size);
		}
		#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);
				}
			}
		}

		class SubmeshTriangleBuffer {
			public int[] triangles = new int[0];

			// These two fields are used when renderMeshes == false
			public int triangleCount;
			public int firstVertex = -1;
		}
	}
}