/****************************************************************************** * 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 { /// Renders a skeleton. [ExecuteAlways, 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 separatorSlots = new List(); 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 customMaterialOverride = new Dictionary(); public Dictionary CustomMaterialOverride { get { return customMaterialOverride; } } #endif // Custom Slot Material [System.NonSerialized] readonly Dictionary customSlotMaterials = new Dictionary(); public Dictionary 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 doubleBufferedMesh; readonly SmartMesh.Instruction currentInstructions = new SmartMesh.Instruction(); readonly ExposedList submeshes = new ExposedList(); readonly ExposedList submeshMaterials = new ExposedList(); 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 (SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer { return SkeletonRenderer.AddSpineComponent(new GameObject("New Spine GameObject"), skeletonDataAsset); } /// Add and prepare a Spine component that derives from SkeletonRenderer to a GameObject at runtime. /// T should be SkeletonRenderer or any of its derived classes. public static T AddSpineComponent (GameObject gameObject, SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer { var c = gameObject.AddComponent(); 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(); 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(); meshRenderer = GetComponent(); doubleBufferedMesh = new DoubleBuffered(); 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 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 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 ///This is a Mesh that also stores the instructions SkeletonRenderer generated for it. 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 attachments = new ExposedList(); public readonly ExposedList submeshInstructions = new ExposedList(); #if SPINE_OPTIONAL_FRONTFACING public bool frontFacing; public readonly ExposedList attachmentFlips = new ExposedList(); #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); } } } } }