diff --git a/CHANGELOG.md b/CHANGELOG.md index 6232a830a..e16a93371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -183,7 +183,8 @@ * Moved `Modules/AnimationMatchModifier` directory to `Spine Examples/Scripts/MecanimAnimationMatchModifier`. * Moved `SkeletonRagdoll` and `SkeletonRagdoll2D` components from `Modules/Ragdoll` directory to `Spine Examples/Scripts/Sample Components/SkeletonUtility Modules`. * Moved `AttachmentTools.cs` to `Utility` directory. - * Split the file `AttachmentTools` into 4 new files for each contained class. No namespace or other API changes performed. + * Split the file `AttachmentTools` into 5 separate files for each contained class. No namespace or other API changes performed. + * Split the file `Mesh Generation/SpineMesh` into 4 separate files for each contained class. No namespace or other API changes performed. * Moved `SkeletonExtensions.cs` to `Utility` directory. * Moved `Modules/YieldInstructions` directory to `Utility/YieldInstructions`. * Moved corresponding editor scripts of the above components to restructured directories as well. diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs new file mode 100644 index 000000000..765b161f1 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs @@ -0,0 +1,1286 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated May 1, 2019. Replaces all prior versions. + * + * Copyright (c) 2013-2019, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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. + *****************************************************************************/ + +// Not for optimization. Do not disable. +#define SPINE_TRIANGLECHECK // Avoid calling SetTriangles at the cost of checking for mesh differences (vertex counts, memberwise attachment list compare) every frame. +//#define SPINE_DEBUG + +using UnityEngine; +using System; +using System.Collections.Generic; + +namespace Spine.Unity { + public delegate void MeshGeneratorDelegate (MeshGeneratorBuffers buffers); + public struct MeshGeneratorBuffers { + /// The vertex count that will actually be used for the mesh. The Lengths of the buffer arrays may be larger than this number. + public int vertexCount; + + /// Vertex positions. To be used for UnityEngine.Mesh.vertices. + public Vector3[] vertexBuffer; + + /// Vertex UVs. To be used for UnityEngine.Mesh.uvs. + public Vector2[] uvBuffer; + + /// Vertex colors. To be used for UnityEngine.Mesh.colors32. + public Color32[] colorBuffer; + + /// The Spine rendering component's MeshGenerator. + public MeshGenerator meshGenerator; + } + + /// Holds several methods to prepare and generate a UnityEngine mesh based on a skeleton. Contains buffers needed to perform the operation, and serializes settings for mesh generation. + [System.Serializable] + public class MeshGenerator { + public Settings settings = Settings.Default; + + [System.Serializable] + public struct Settings { + public bool useClipping; + [Space] + [Range(-0.1f, 0f)] public float zSpacing; + [Space] + [Header("Vertex Data")] + public bool pmaVertexColors; + public bool tintBlack; + public bool calculateTangents; + public bool addNormals; + public bool immutableTriangles; + + static public Settings Default { + get { + return new Settings { + pmaVertexColors = true, + zSpacing = 0f, + useClipping = true, + tintBlack = false, + calculateTangents = false, + //renderMeshes = true, + addNormals = false, + immutableTriangles = false + }; + } + } + } + + const float BoundsMinDefault = float.PositiveInfinity; + const float BoundsMaxDefault = float.NegativeInfinity; + + [NonSerialized] readonly ExposedList vertexBuffer = new ExposedList(4); + [NonSerialized] readonly ExposedList uvBuffer = new ExposedList(4); + [NonSerialized] readonly ExposedList colorBuffer = new ExposedList(4); + [NonSerialized] readonly ExposedList> submeshes = new ExposedList> { new ExposedList(6) }; // start with 1 submesh. + + [NonSerialized] Vector2 meshBoundsMin, meshBoundsMax; + [NonSerialized] float meshBoundsThickness; + [NonSerialized] int submeshIndex = 0; + + [NonSerialized] SkeletonClipping clipper = new SkeletonClipping(); + [NonSerialized] float[] tempVerts = new float[8]; + [NonSerialized] int[] regionTriangles = { 0, 1, 2, 2, 3, 0 }; + + #region Optional Buffers + // These optional buffers are lazy-instantiated when the feature is used. + [NonSerialized] Vector3[] normals; + [NonSerialized] Vector4[] tangents; + [NonSerialized] Vector2[] tempTanBuffer; + [NonSerialized] ExposedList uv2; + [NonSerialized] ExposedList uv3; + #endregion + + public int VertexCount { get { return vertexBuffer.Count; } } + + /// A set of mesh arrays whose values are modifiable by the user. Modify these values before they are passed to the UnityEngine mesh object in order to see the effect. + public MeshGeneratorBuffers Buffers { + get { + return new MeshGeneratorBuffers { + vertexCount = this.VertexCount, + vertexBuffer = this.vertexBuffer.Items, + uvBuffer = this.uvBuffer.Items, + colorBuffer = this.colorBuffer.Items, + meshGenerator = this + }; + } + } + + public MeshGenerator () { + submeshes.TrimExcess(); + } + + #region Step 1 : Generate Instructions + public static void GenerateSingleSubmeshInstruction (SkeletonRendererInstruction instructionOutput, Skeleton skeleton, Material material) { + ExposedList drawOrder = skeleton.drawOrder; + int drawOrderCount = drawOrder.Count; + + // Clear last state of attachments and submeshes + instructionOutput.Clear(); // submeshInstructions.Clear(); attachments.Clear(); + var workingSubmeshInstructions = instructionOutput.submeshInstructions; + workingSubmeshInstructions.Resize(1); + + #if SPINE_TRIANGLECHECK + instructionOutput.attachments.Resize(drawOrderCount); + var workingAttachmentsItems = instructionOutput.attachments.Items; + int totalRawVertexCount = 0; + #endif + + var current = new SubmeshInstruction { + skeleton = skeleton, + preActiveClippingSlotSource = -1, + startSlot = 0, + #if SPINE_TRIANGLECHECK + rawFirstVertexIndex = 0, + #endif + material = material, + forceSeparate = false, + endSlot = drawOrderCount + }; + + #if SPINE_TRIANGLECHECK + bool skeletonHasClipping = false; + var drawOrderItems = drawOrder.Items; + for (int i = 0; i < drawOrderCount; i++) { + Slot slot = drawOrderItems[i]; + if (!slot.bone.active) continue; + Attachment attachment = slot.attachment; + + workingAttachmentsItems[i] = attachment; + int attachmentTriangleCount; + int attachmentVertexCount; + + var regionAttachment = attachment as RegionAttachment; + if (regionAttachment != null) { + attachmentVertexCount = 4; + attachmentTriangleCount = 6; + } else { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) { + attachmentVertexCount = meshAttachment.worldVerticesLength >> 1; + attachmentTriangleCount = meshAttachment.triangles.Length; + } else { + var clippingAttachment = attachment as ClippingAttachment; + if (clippingAttachment != null) { + current.hasClipping = true; + skeletonHasClipping = true; + } + attachmentVertexCount = 0; + attachmentTriangleCount = 0; + } + } + current.rawTriangleCount += attachmentTriangleCount; + current.rawVertexCount += attachmentVertexCount; + totalRawVertexCount += attachmentVertexCount; + + } + + instructionOutput.hasActiveClipping = skeletonHasClipping; + instructionOutput.rawVertexCount = totalRawVertexCount; + #endif + + workingSubmeshInstructions.Items[0] = current; + } + + public static void GenerateSkeletonRendererInstruction (SkeletonRendererInstruction instructionOutput, Skeleton skeleton, Dictionary customSlotMaterials, List separatorSlots, bool generateMeshOverride, bool immutableTriangles = false) { + // if (skeleton == null) throw new ArgumentNullException("skeleton"); + // if (instructionOutput == null) throw new ArgumentNullException("instructionOutput"); + + ExposedList drawOrder = skeleton.drawOrder; + int drawOrderCount = drawOrder.Count; + + // Clear last state of attachments and submeshes + instructionOutput.Clear(); // submeshInstructions.Clear(); attachments.Clear(); + var workingSubmeshInstructions = instructionOutput.submeshInstructions; + #if SPINE_TRIANGLECHECK + instructionOutput.attachments.Resize(drawOrderCount); + var workingAttachmentsItems = instructionOutput.attachments.Items; + int totalRawVertexCount = 0; + bool skeletonHasClipping = false; + #endif + + var current = new SubmeshInstruction { + skeleton = skeleton, + preActiveClippingSlotSource = -1 + }; + + #if !SPINE_TK2D + bool isCustomSlotMaterialsPopulated = customSlotMaterials != null && customSlotMaterials.Count > 0; + #endif + + int separatorCount = separatorSlots == null ? 0 : separatorSlots.Count; + bool hasSeparators = separatorCount > 0; + + int clippingAttachmentSource = -1; + int lastPreActiveClipping = -1; // The index of the last slot that had an active ClippingAttachment. + SlotData clippingEndSlot = null; + int submeshIndex = 0; + var drawOrderItems = drawOrder.Items; + for (int i = 0; i < drawOrderCount; i++) { + Slot slot = drawOrderItems[i]; + if (!slot.bone.active) continue; + Attachment attachment = slot.attachment; + #if SPINE_TRIANGLECHECK + workingAttachmentsItems[i] = attachment; + int attachmentVertexCount = 0, attachmentTriangleCount = 0; + #endif + + object rendererObject = null; // An AtlasRegion in plain Spine-Unity. Spine-TK2D hooks into TK2D's system. eventual source of Material object. + bool noRender = false; // Using this allows empty slots as separators, and keeps separated parts more stable despite slots being reordered + + var regionAttachment = attachment as RegionAttachment; + if (regionAttachment != null) { + rendererObject = regionAttachment.RendererObject; + #if SPINE_TRIANGLECHECK + attachmentVertexCount = 4; + attachmentTriangleCount = 6; + #endif + } else { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) { + rendererObject = meshAttachment.RendererObject; + #if SPINE_TRIANGLECHECK + attachmentVertexCount = meshAttachment.worldVerticesLength >> 1; + attachmentTriangleCount = meshAttachment.triangles.Length; + #endif + } else { + #if SPINE_TRIANGLECHECK + var clippingAttachment = attachment as ClippingAttachment; + if (clippingAttachment != null) { + clippingEndSlot = clippingAttachment.endSlot; + clippingAttachmentSource = i; + current.hasClipping = true; + skeletonHasClipping = true; + } + #endif + noRender = true; + } + } + + if (clippingEndSlot != null && slot.data == clippingEndSlot && i != clippingAttachmentSource) { + clippingEndSlot = null; + clippingAttachmentSource = -1; + } + + // Create a new SubmeshInstruction when material changes. (or when forced to separate by a submeshSeparator) + // Slot with a separator/new material will become the starting slot of the next new instruction. + if (hasSeparators) { //current.forceSeparate = hasSeparators && separatorSlots.Contains(slot); + current.forceSeparate = false; + for (int s = 0; s < separatorCount; s++) { + if (Slot.ReferenceEquals(slot, separatorSlots[s])) { + current.forceSeparate = true; + break; + } + } + } + + if (noRender) { + if (current.forceSeparate && generateMeshOverride) { // && current.rawVertexCount > 0) { + { // Add + current.endSlot = i; + current.preActiveClippingSlotSource = lastPreActiveClipping; + + workingSubmeshInstructions.Resize(submeshIndex + 1); + workingSubmeshInstructions.Items[submeshIndex] = current; + + submeshIndex++; + } + + current.startSlot = i; + lastPreActiveClipping = clippingAttachmentSource; + #if SPINE_TRIANGLECHECK + current.rawTriangleCount = 0; + current.rawVertexCount = 0; + current.rawFirstVertexIndex = totalRawVertexCount; + current.hasClipping = clippingAttachmentSource >= 0; + #endif + } + } else { + #if !SPINE_TK2D + Material material; + if (isCustomSlotMaterialsPopulated) { + if (!customSlotMaterials.TryGetValue(slot, out material)) + material = (Material)((AtlasRegion)rendererObject).page.rendererObject; + } else { + material = (Material)((AtlasRegion)rendererObject).page.rendererObject; + } + #else + Material material = (rendererObject is Material) ? (Material)rendererObject : (Material)((AtlasRegion)rendererObject).page.rendererObject; + #endif + + if (current.forceSeparate || (current.rawVertexCount > 0 && !System.Object.ReferenceEquals(current.material, material))) { // Material changed. Add the previous submesh. + { // Add + current.endSlot = i; + current.preActiveClippingSlotSource = lastPreActiveClipping; + + workingSubmeshInstructions.Resize(submeshIndex + 1); + workingSubmeshInstructions.Items[submeshIndex] = current; + submeshIndex++; + } + current.startSlot = i; + lastPreActiveClipping = clippingAttachmentSource; + #if SPINE_TRIANGLECHECK + current.rawTriangleCount = 0; + current.rawVertexCount = 0; + current.rawFirstVertexIndex = totalRawVertexCount; + current.hasClipping = clippingAttachmentSource >= 0; + #endif + } + + // Update state for the next Attachment. + current.material = material; + #if SPINE_TRIANGLECHECK + current.rawTriangleCount += attachmentTriangleCount; + current.rawVertexCount += attachmentVertexCount; + current.rawFirstVertexIndex = totalRawVertexCount; + totalRawVertexCount += attachmentVertexCount; + #endif + } + } + + if (current.rawVertexCount > 0) { + { // Add last or only submesh. + current.endSlot = drawOrderCount; + current.preActiveClippingSlotSource = lastPreActiveClipping; + current.forceSeparate = false; + + workingSubmeshInstructions.Resize(submeshIndex + 1); + workingSubmeshInstructions.Items[submeshIndex] = current; + //submeshIndex++; + } + } + + #if SPINE_TRIANGLECHECK + instructionOutput.hasActiveClipping = skeletonHasClipping; + instructionOutput.rawVertexCount = totalRawVertexCount; + #endif + instructionOutput.immutableTriangles = immutableTriangles; + } + + public static void TryReplaceMaterials (ExposedList workingSubmeshInstructions, Dictionary customMaterialOverride) { + // Material overrides are done here so they can be applied per submesh instead of per slot + // but they will still be passed through the GenerateMeshOverride delegate, + // and will still go through the normal material match check step in STEP 3. + var wsii = workingSubmeshInstructions.Items; + for (int i = 0; i < workingSubmeshInstructions.Count; i++) { + var m = wsii[i].material; + Material mo; + if (customMaterialOverride.TryGetValue(m, out mo)) + wsii[i].material = mo; + } + } + #endregion + + #region Step 2 : Populate vertex data and triangle index buffers. + public void Begin () { + vertexBuffer.Clear(false); + colorBuffer.Clear(false); + uvBuffer.Clear(false); + clipper.ClipEnd(); + + { + meshBoundsMin.x = BoundsMinDefault; + meshBoundsMin.y = BoundsMinDefault; + meshBoundsMax.x = BoundsMaxDefault; + meshBoundsMax.y = BoundsMaxDefault; + meshBoundsThickness = 0f; + } + + submeshIndex = 0; + submeshes.Count = 1; + //submeshes.Items[0].Clear(false); + } + + public void AddSubmesh (SubmeshInstruction instruction, bool updateTriangles = true) { + var settings = this.settings; + + int newSubmeshCount = submeshIndex + 1; + if (submeshes.Items.Length < newSubmeshCount) + submeshes.Resize(newSubmeshCount); + submeshes.Count = newSubmeshCount; + var submesh = submeshes.Items[submeshIndex]; + if (submesh == null) + submeshes.Items[submeshIndex] = submesh = new ExposedList(); + submesh.Clear(false); + + var skeleton = instruction.skeleton; + var drawOrderItems = skeleton.drawOrder.Items; + + Color32 color = default(Color32); + float skeletonA = skeleton.a * 255, skeletonR = skeleton.r, skeletonG = skeleton.g, skeletonB = skeleton.b; + Vector2 meshBoundsMin = this.meshBoundsMin, meshBoundsMax = this.meshBoundsMax; + + // Settings + float zSpacing = settings.zSpacing; + bool pmaVertexColors = settings.pmaVertexColors; + bool tintBlack = settings.tintBlack; + #if SPINE_TRIANGLECHECK + bool useClipping = settings.useClipping && instruction.hasClipping; + #else + bool useClipping = settings.useClipping; + #endif + + if (useClipping) { + if (instruction.preActiveClippingSlotSource >= 0) { + var slot = drawOrderItems[instruction.preActiveClippingSlotSource]; + clipper.ClipStart(slot, slot.attachment as ClippingAttachment); + } + } + + for (int slotIndex = instruction.startSlot; slotIndex < instruction.endSlot; slotIndex++) { + var slot = drawOrderItems[slotIndex]; + if (!slot.bone.active) continue; + var attachment = slot.attachment; + float z = zSpacing * slotIndex; + + var workingVerts = this.tempVerts; + float[] uvs; + int[] attachmentTriangleIndices; + int attachmentVertexCount; + int attachmentIndexCount; + + Color c = default(Color); + + // Identify and prepare values. + var region = attachment as RegionAttachment; + if (region != null) { + region.ComputeWorldVertices(slot.bone, workingVerts, 0); + uvs = region.uvs; + attachmentTriangleIndices = regionTriangles; + c.r = region.r; c.g = region.g; c.b = region.b; c.a = region.a; + attachmentVertexCount = 4; + attachmentIndexCount = 6; + } else { + var mesh = attachment as MeshAttachment; + if (mesh != null) { + int meshVerticesLength = mesh.worldVerticesLength; + if (workingVerts.Length < meshVerticesLength) { + workingVerts = new float[meshVerticesLength]; + this.tempVerts = workingVerts; + } + mesh.ComputeWorldVertices(slot, 0, meshVerticesLength, workingVerts, 0); //meshAttachment.ComputeWorldVertices(slot, tempVerts); + uvs = mesh.uvs; + attachmentTriangleIndices = mesh.triangles; + c.r = mesh.r; c.g = mesh.g; c.b = mesh.b; c.a = mesh.a; + attachmentVertexCount = meshVerticesLength >> 1; // meshVertexCount / 2; + attachmentIndexCount = mesh.triangles.Length; + } else { + if (useClipping) { + var clippingAttachment = attachment as ClippingAttachment; + if (clippingAttachment != null) { + clipper.ClipStart(slot, clippingAttachment); + continue; + } + } + + // If not any renderable attachment. + clipper.ClipEnd(slot); + continue; + } + } + + if (pmaVertexColors) { + color.a = (byte)(skeletonA * slot.a * c.a); + color.r = (byte)(skeletonR * slot.r * c.r * color.a); + color.g = (byte)(skeletonG * slot.g * c.g * color.a); + color.b = (byte)(skeletonB * slot.b * c.b * color.a); + if (slot.data.blendMode == BlendMode.Additive) color.a = 0; + } else { + color.a = (byte)(skeletonA * slot.a * c.a); + color.r = (byte)(skeletonR * slot.r * c.r * 255); + color.g = (byte)(skeletonG * slot.g * c.g * 255); + color.b = (byte)(skeletonB * slot.b * c.b * 255); + } + + if (useClipping && clipper.IsClipping) { + clipper.ClipTriangles(workingVerts, attachmentVertexCount << 1, attachmentTriangleIndices, attachmentIndexCount, uvs); + workingVerts = clipper.clippedVertices.Items; + attachmentVertexCount = clipper.clippedVertices.Count >> 1; + attachmentTriangleIndices = clipper.clippedTriangles.Items; + attachmentIndexCount = clipper.clippedTriangles.Count; + uvs = clipper.clippedUVs.Items; + } + + // Actually add slot/attachment data into buffers. + if (attachmentVertexCount != 0 && attachmentIndexCount != 0) { + if (tintBlack) + AddAttachmentTintBlack(slot.r2, slot.g2, slot.b2, attachmentVertexCount); + + //AddAttachment(workingVerts, uvs, color, attachmentTriangleIndices, attachmentVertexCount, attachmentIndexCount, ref meshBoundsMin, ref meshBoundsMax, z); + int ovc = vertexBuffer.Count; + // Add data to vertex buffers + { + int newVertexCount = ovc + attachmentVertexCount; + int oldArraySize = vertexBuffer.Items.Length; + if (newVertexCount > oldArraySize) { + int newArraySize = (int)(oldArraySize * 1.3f); + if (newArraySize < newVertexCount) newArraySize = newVertexCount; + Array.Resize(ref vertexBuffer.Items, newArraySize); + Array.Resize(ref uvBuffer.Items, newArraySize); + Array.Resize(ref colorBuffer.Items, newArraySize); + } + vertexBuffer.Count = uvBuffer.Count = colorBuffer.Count = newVertexCount; + } + + var vbi = vertexBuffer.Items; + var ubi = uvBuffer.Items; + var cbi = colorBuffer.Items; + if (ovc == 0) { + for (int i = 0; i < attachmentVertexCount; i++) { + int vi = ovc + i; + int i2 = i << 1; // i * 2 + float x = workingVerts[i2]; + float y = workingVerts[i2 + 1]; + + vbi[vi].x = x; + vbi[vi].y = y; + vbi[vi].z = z; + ubi[vi].x = uvs[i2]; + ubi[vi].y = uvs[i2 + 1]; + cbi[vi] = color; + + // Calculate bounds. + if (x < meshBoundsMin.x) meshBoundsMin.x = x; + if (x > meshBoundsMax.x) meshBoundsMax.x = x; + if (y < meshBoundsMin.y) meshBoundsMin.y = y; + if (y > meshBoundsMax.y) meshBoundsMax.y = y; + } + } else { + for (int i = 0; i < attachmentVertexCount; i++) { + int vi = ovc + i; + int i2 = i << 1; // i * 2 + float x = workingVerts[i2]; + float y = workingVerts[i2 + 1]; + + vbi[vi].x = x; + vbi[vi].y = y; + vbi[vi].z = z; + ubi[vi].x = uvs[i2]; + ubi[vi].y = uvs[i2 + 1]; + cbi[vi] = color; + + // Calculate bounds. + if (x < meshBoundsMin.x) meshBoundsMin.x = x; + else if (x > meshBoundsMax.x) meshBoundsMax.x = x; + if (y < meshBoundsMin.y) meshBoundsMin.y = y; + else if (y > meshBoundsMax.y) meshBoundsMax.y = y; + } + } + + + // Add data to triangle buffer + if (updateTriangles) { + int oldTriangleCount = submesh.Count; + { //submesh.Resize(oldTriangleCount + attachmentIndexCount); + int newTriangleCount = oldTriangleCount + attachmentIndexCount; + if (newTriangleCount > submesh.Items.Length) Array.Resize(ref submesh.Items, newTriangleCount); + submesh.Count = newTriangleCount; + } + var submeshItems = submesh.Items; + for (int i = 0; i < attachmentIndexCount; i++) + submeshItems[oldTriangleCount + i] = attachmentTriangleIndices[i] + ovc; + } + } + + clipper.ClipEnd(slot); + } + clipper.ClipEnd(); + + this.meshBoundsMin = meshBoundsMin; + this.meshBoundsMax = meshBoundsMax; + meshBoundsThickness = instruction.endSlot * zSpacing; + + // Trim or zero submesh triangles. + var currentSubmeshItems = submesh.Items; + for (int i = submesh.Count, n = currentSubmeshItems.Length; i < n; i++) + currentSubmeshItems[i] = 0; + + submeshIndex++; // Next AddSubmesh will use a new submeshIndex value. + } + + public void BuildMesh (SkeletonRendererInstruction instruction, bool updateTriangles) { + var wsii = instruction.submeshInstructions.Items; + for (int i = 0, n = instruction.submeshInstructions.Count; i < n; i++) + this.AddSubmesh(wsii[i], updateTriangles); + } + + // Use this faster method when no clipping is involved. + public void BuildMeshWithArrays (SkeletonRendererInstruction instruction, bool updateTriangles) { + var settings = this.settings; + int totalVertexCount = instruction.rawVertexCount; + + // Add data to vertex buffers + { + if (totalVertexCount > vertexBuffer.Items.Length) { // Manual ExposedList.Resize() + Array.Resize(ref vertexBuffer.Items, totalVertexCount); + Array.Resize(ref uvBuffer.Items, totalVertexCount); + Array.Resize(ref colorBuffer.Items, totalVertexCount); + } + vertexBuffer.Count = uvBuffer.Count = colorBuffer.Count = totalVertexCount; + } + + // Populate Verts + Color32 color = default(Color32); + + int vertexIndex = 0; + var tempVerts = this.tempVerts; + Vector2 bmin = this.meshBoundsMin; + Vector2 bmax = this.meshBoundsMax; + + var vbi = vertexBuffer.Items; + var ubi = uvBuffer.Items; + var cbi = colorBuffer.Items; + int lastSlotIndex = 0; + + // drawOrder[endSlot] is excluded + for (int si = 0, n = instruction.submeshInstructions.Count; si < n; si++) { + var submesh = instruction.submeshInstructions.Items[si]; + var skeleton = submesh.skeleton; + var drawOrderItems = skeleton.drawOrder.Items; + float a = skeleton.a * 255, r = skeleton.r, g = skeleton.g, b = skeleton.b; + + int endSlot = submesh.endSlot; + int startSlot = submesh.startSlot; + lastSlotIndex = endSlot; + + if (settings.tintBlack) { + Vector2 rg, b2; + int vi = vertexIndex; + b2.y = 1f; + + { + if (uv2 == null) { + uv2 = new ExposedList(); + uv3 = new ExposedList(); + } + if (totalVertexCount > uv2.Items.Length) { // Manual ExposedList.Resize() + Array.Resize(ref uv2.Items, totalVertexCount); + Array.Resize(ref uv3.Items, totalVertexCount); + } + uv2.Count = uv3.Count = totalVertexCount; + } + + var uv2i = uv2.Items; + var uv3i = uv3.Items; + + for (int slotIndex = startSlot; slotIndex < endSlot; slotIndex++) { + var slot = drawOrderItems[slotIndex]; + if (!slot.bone.active) continue; + var attachment = slot.attachment; + + rg.x = slot.r2; //r + rg.y = slot.g2; //g + b2.x = slot.b2; //b + + var regionAttachment = attachment as RegionAttachment; + if (regionAttachment != null) { + uv2i[vi] = rg; uv2i[vi + 1] = rg; uv2i[vi + 2] = rg; uv2i[vi + 3] = rg; + uv3i[vi] = b2; uv3i[vi + 1] = b2; uv3i[vi + 2] = b2; uv3i[vi + 3] = b2; + vi += 4; + } else { //} if (settings.renderMeshes) { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) { + int meshVertexCount = meshAttachment.worldVerticesLength; + for (int iii = 0; iii < meshVertexCount; iii += 2) { + uv2i[vi] = rg; + uv3i[vi] = b2; + vi++; + } + } + } + } + } + + for (int slotIndex = startSlot; slotIndex < endSlot; slotIndex++) { + var slot = drawOrderItems[slotIndex]; + if (!slot.bone.active) continue; + var attachment = slot.attachment; + float z = slotIndex * settings.zSpacing; + + var regionAttachment = attachment as RegionAttachment; + if (regionAttachment != null) { + regionAttachment.ComputeWorldVertices(slot.bone, tempVerts, 0); + + float x1 = tempVerts[RegionAttachment.BLX], y1 = tempVerts[RegionAttachment.BLY]; + float x2 = tempVerts[RegionAttachment.ULX], y2 = tempVerts[RegionAttachment.ULY]; + float x3 = tempVerts[RegionAttachment.URX], y3 = tempVerts[RegionAttachment.URY]; + float x4 = tempVerts[RegionAttachment.BRX], y4 = tempVerts[RegionAttachment.BRY]; + vbi[vertexIndex].x = x1; vbi[vertexIndex].y = y1; vbi[vertexIndex].z = z; + vbi[vertexIndex + 1].x = x4; vbi[vertexIndex + 1].y = y4; vbi[vertexIndex + 1].z = z; + vbi[vertexIndex + 2].x = x2; vbi[vertexIndex + 2].y = y2; vbi[vertexIndex + 2].z = z; + vbi[vertexIndex + 3].x = x3; vbi[vertexIndex + 3].y = y3; vbi[vertexIndex + 3].z = z; + + if (settings.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); + } + + cbi[vertexIndex] = color; cbi[vertexIndex + 1] = color; cbi[vertexIndex + 2] = color; cbi[vertexIndex + 3] = color; + + float[] regionUVs = regionAttachment.uvs; + ubi[vertexIndex].x = regionUVs[RegionAttachment.BLX]; ubi[vertexIndex].y = regionUVs[RegionAttachment.BLY]; + ubi[vertexIndex + 1].x = regionUVs[RegionAttachment.BRX]; ubi[vertexIndex + 1].y = regionUVs[RegionAttachment.BRY]; + ubi[vertexIndex + 2].x = regionUVs[RegionAttachment.ULX]; ubi[vertexIndex + 2].y = regionUVs[RegionAttachment.ULY]; + ubi[vertexIndex + 3].x = regionUVs[RegionAttachment.URX]; ubi[vertexIndex + 3].y = regionUVs[RegionAttachment.URY]; + + if (x1 < bmin.x) bmin.x = x1; // Potential first attachment bounds initialization. Initial min should not block initial max. Same for Y below. + if (x1 > bmax.x) bmax.x = x1; + if (x2 < bmin.x) bmin.x = x2; + else if (x2 > bmax.x) bmax.x = x2; + if (x3 < bmin.x) bmin.x = x3; + else if (x3 > bmax.x) bmax.x = x3; + if (x4 < bmin.x) bmin.x = x4; + else if (x4 > bmax.x) bmax.x = x4; + + if (y1 < bmin.y) bmin.y = y1; + if (y1 > bmax.y) bmax.y = y1; + if (y2 < bmin.y) bmin.y = y2; + else if (y2 > bmax.y) bmax.y = y2; + if (y3 < bmin.y) bmin.y = y3; + else if (y3 > bmax.y) bmax.y = y3; + if (y4 < bmin.y) bmin.y = y4; + else if (y4 > bmax.y) bmax.y = y4; + + vertexIndex += 4; + } else { //if (settings.renderMeshes) { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) { + int meshVertexCount = meshAttachment.worldVerticesLength; + if (tempVerts.Length < meshVertexCount) this.tempVerts = tempVerts = new float[meshVertexCount]; + meshAttachment.ComputeWorldVertices(slot, tempVerts); + + if (settings.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[] attachmentUVs = meshAttachment.uvs; + + // Potential first attachment bounds initialization. See conditions in RegionAttachment logic. + if (vertexIndex == 0) { + // Initial min should not block initial max. + // vi == vertexIndex does not always mean the bounds are fresh. It could be a submesh. Do not nuke old values by omitting the check. + // Should know that this is the first attachment in the submesh. slotIndex == startSlot could be an empty slot. + float fx = tempVerts[0], fy = tempVerts[1]; + if (fx < bmin.x) bmin.x = fx; + if (fx > bmax.x) bmax.x = fx; + if (fy < bmin.y) bmin.y = fy; + if (fy > bmax.y) bmax.y = fy; + } + + for (int iii = 0; iii < meshVertexCount; iii += 2) { + float x = tempVerts[iii], y = tempVerts[iii + 1]; + vbi[vertexIndex].x = x; vbi[vertexIndex].y = y; vbi[vertexIndex].z = z; + cbi[vertexIndex] = color; ubi[vertexIndex].x = attachmentUVs[iii]; ubi[vertexIndex].y = attachmentUVs[iii + 1]; + + if (x < bmin.x) bmin.x = x; + else if (x > bmax.x) bmax.x = x; + + if (y < bmin.y) bmin.y = y; + else if (y > bmax.y) bmax.y = y; + + vertexIndex++; + } + } + } + } + } + + this.meshBoundsMin = bmin; + this.meshBoundsMax = bmax; + this.meshBoundsThickness = lastSlotIndex * settings.zSpacing; + + int submeshInstructionCount = instruction.submeshInstructions.Count; + submeshes.Count = submeshInstructionCount; + + // Add triangles + if (updateTriangles) { + // Match submesh buffers count with submeshInstruction count. + if (this.submeshes.Items.Length < submeshInstructionCount) { + this.submeshes.Resize(submeshInstructionCount); + for (int i = 0, n = submeshInstructionCount; i < n; i++) { + var submeshBuffer = this.submeshes.Items[i]; + if (submeshBuffer == null) + this.submeshes.Items[i] = new ExposedList(); + else + submeshBuffer.Clear(false); + } + } + + var submeshInstructionsItems = instruction.submeshInstructions.Items; // This relies on the resize above. + + // Fill the buffers. + int attachmentFirstVertex = 0; + for (int smbi = 0; smbi < submeshInstructionCount; smbi++) { + var submeshInstruction = submeshInstructionsItems[smbi]; + var currentSubmeshBuffer = this.submeshes.Items[smbi]; + { //submesh.Resize(submesh.rawTriangleCount); + int newTriangleCount = submeshInstruction.rawTriangleCount; + if (newTriangleCount > currentSubmeshBuffer.Items.Length) + Array.Resize(ref currentSubmeshBuffer.Items, newTriangleCount); + else if (newTriangleCount < currentSubmeshBuffer.Items.Length) { + // Zero the extra. + var sbi = currentSubmeshBuffer.Items; + for (int ei = newTriangleCount, nn = sbi.Length; ei < nn; ei++) + sbi[ei] = 0; + } + currentSubmeshBuffer.Count = newTriangleCount; + } + + var tris = currentSubmeshBuffer.Items; + int triangleIndex = 0; + var skeleton = submeshInstruction.skeleton; + var drawOrderItems = skeleton.drawOrder.Items; + for (int slotIndex = submeshInstruction.startSlot, endSlot = submeshInstruction.endSlot; slotIndex < endSlot; slotIndex++) { + var slot = drawOrderItems[slotIndex]; + if (!slot.bone.active) continue; + + var attachment = drawOrderItems[slotIndex].attachment; + if (attachment is RegionAttachment) { + tris[triangleIndex] = attachmentFirstVertex; + tris[triangleIndex + 1] = attachmentFirstVertex + 2; + tris[triangleIndex + 2] = attachmentFirstVertex + 1; + tris[triangleIndex + 3] = attachmentFirstVertex + 2; + tris[triangleIndex + 4] = attachmentFirstVertex + 3; + tris[triangleIndex + 5] = attachmentFirstVertex + 1; + triangleIndex += 6; + attachmentFirstVertex += 4; + continue; + } + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) { + int[] attachmentTriangles = meshAttachment.triangles; + for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, triangleIndex++) + tris[triangleIndex] = attachmentFirstVertex + attachmentTriangles[ii]; + attachmentFirstVertex += meshAttachment.worldVerticesLength >> 1; // length/2; + } + } + } + } + } + + public void ScaleVertexData (float scale) { + var vbi = vertexBuffer.Items; + for (int i = 0, n = vertexBuffer.Count; i < n; i++) { + vbi[i] *= scale; // vbi[i].x *= scale; vbi[i].y *= scale; + } + + meshBoundsMin *= scale; + meshBoundsMax *= scale; + meshBoundsThickness *= scale; + } + + void AddAttachmentTintBlack (float r2, float g2, float b2, int vertexCount) { + var rg = new Vector2(r2, g2); + var bo = new Vector2(b2, 1f); + + int ovc = vertexBuffer.Count; + int newVertexCount = ovc + vertexCount; + { + if (uv2 == null) { + uv2 = new ExposedList(); + uv3 = new ExposedList(); + } + if (newVertexCount > uv2.Items.Length) { // Manual ExposedList.Resize() + Array.Resize(ref uv2.Items, newVertexCount); + Array.Resize(ref uv3.Items, newVertexCount); + } + uv2.Count = uv3.Count = newVertexCount; + } + + var uv2i = uv2.Items; + var uv3i = uv3.Items; + for (int i = 0; i < vertexCount; i++) { + uv2i[ovc + i] = rg; + uv3i[ovc + i] = bo; + } + } + #endregion + + #region Step 3 : Transfer vertex and triangle data to UnityEngine.Mesh + public void FillVertexData (Mesh mesh) { + var vbi = vertexBuffer.Items; + var ubi = uvBuffer.Items; + var cbi = colorBuffer.Items; + int vbiLength = vbi.Length; + + // Zero the extra. + { + int listCount = vertexBuffer.Count; + var vector3zero = Vector3.zero; + for (int i = listCount; i < vbiLength; i++) + vbi[i] = vector3zero; + } + + // Set the vertex buffer. + { + mesh.vertices = vbi; + mesh.uv = ubi; + mesh.colors32 = cbi; + + if (float.IsInfinity(meshBoundsMin.x)) { // meshBoundsMin.x == BoundsMinDefault // == doesn't work on float Infinity constants. + mesh.bounds = new Bounds(); + } else { + //mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax); + float halfWidth = (meshBoundsMax.x - meshBoundsMin.x) * 0.5f; + float halfHeight = (meshBoundsMax.y - meshBoundsMin.y) * 0.5f; + mesh.bounds = new Bounds { + center = new Vector3(meshBoundsMin.x + halfWidth, meshBoundsMin.y + halfHeight), + extents = new Vector3(halfWidth, halfHeight, meshBoundsThickness * 0.5f) + }; + } + } + + { + if (settings.addNormals) { + int oldLength = 0; + + if (normals == null) + normals = new Vector3[vbiLength]; + else + oldLength = normals.Length; + + if (oldLength != vbiLength) { + Array.Resize(ref this.normals, vbiLength); + var localNormals = this.normals; + for (int i = oldLength; i < vbiLength; i++) localNormals[i] = Vector3.back; + } + mesh.normals = this.normals; + } + + if (settings.tintBlack) { + if (uv2 != null) { + // Sometimes, the vertex buffer becomes smaller. We need to trim the size of the tint black buffers to match. + if (vbiLength != uv2.Items.Length) { + Array.Resize(ref uv2.Items, vbiLength); + Array.Resize(ref uv3.Items, vbiLength); + uv2.Count = uv3.Count = vbiLength; + } + mesh.uv2 = this.uv2.Items; + mesh.uv3 = this.uv3.Items; + } + } + } + } + + public void FillLateVertexData (Mesh mesh) { + if (settings.calculateTangents) { + int vertexCount = this.vertexBuffer.Count; + var sbi = submeshes.Items; + int submeshCount = submeshes.Count; + var vbi = vertexBuffer.Items; + var ubi = uvBuffer.Items; + + MeshGenerator.SolveTangents2DEnsureSize(ref this.tangents, ref this.tempTanBuffer, vertexCount, vbi.Length); + for (int i = 0; i < submeshCount; i++) { + var submesh = sbi[i].Items; + int triangleCount = sbi[i].Count; + MeshGenerator.SolveTangents2DTriangles(this.tempTanBuffer, submesh, triangleCount, vbi, ubi, vertexCount); + } + MeshGenerator.SolveTangents2DBuffer(this.tangents, this.tempTanBuffer, vertexCount); + mesh.tangents = this.tangents; + } + } + + public void FillTriangles (Mesh mesh) { + int submeshCount = submeshes.Count; + var submeshesItems = submeshes.Items; + mesh.subMeshCount = submeshCount; + + for (int i = 0; i < submeshCount; i++) + mesh.SetTriangles(submeshesItems[i].Items, i, false); + } + + public void FillTrianglesSingle (Mesh mesh) { + mesh.SetTriangles(submeshes.Items[0].Items, 0, false); + } + #endregion + + public void EnsureVertexCapacity (int minimumVertexCount, bool inlcudeTintBlack = false, bool includeTangents = false, bool includeNormals = false) { + if (minimumVertexCount > vertexBuffer.Items.Length) { + Array.Resize(ref vertexBuffer.Items, minimumVertexCount); + Array.Resize(ref uvBuffer.Items, minimumVertexCount); + Array.Resize(ref colorBuffer.Items, minimumVertexCount); + + if (inlcudeTintBlack) { + if (uv2 == null) { + uv2 = new ExposedList(minimumVertexCount); + uv3 = new ExposedList(minimumVertexCount); + } + uv2.Resize(minimumVertexCount); + uv3.Resize(minimumVertexCount); + } + + if (includeNormals) { + if (normals == null) + normals = new Vector3[minimumVertexCount]; + else + Array.Resize(ref normals, minimumVertexCount); + + } + + if (includeTangents) { + if (tangents == null) + tangents = new Vector4[minimumVertexCount]; + else + Array.Resize(ref tangents, minimumVertexCount); + } + } + } + + /// Trims internal buffers to reduce the resulting mesh data stream size. + public void TrimExcess () { + vertexBuffer.TrimExcess(); + uvBuffer.TrimExcess(); + colorBuffer.TrimExcess(); + + if (uv2 != null) uv2.TrimExcess(); + if (uv3 != null) uv3.TrimExcess(); + + int vbiLength = vertexBuffer.Items.Length; + if (normals != null) Array.Resize(ref normals, vbiLength); + if (tangents != null) Array.Resize(ref tangents, vbiLength); + } + + #region TangentSolver2D + // Thanks to contributions from forum user ToddRivers + + /// Step 1 of solving tangents. Ensure you have buffers of the correct size. + /// Eventual Vector4[] tangent buffer to assign to Mesh.tangents. + /// Temporary Vector2 buffer for calculating directions. + /// Number of vertices that require tangents (or the size of the vertex array) + internal static void SolveTangents2DEnsureSize (ref Vector4[] tangentBuffer, ref Vector2[] tempTanBuffer, int vertexCount, int vertexBufferLength) { + if (tangentBuffer == null || tangentBuffer.Length != vertexBufferLength) + tangentBuffer = new Vector4[vertexBufferLength]; + + if (tempTanBuffer == null || tempTanBuffer.Length < vertexCount * 2) + tempTanBuffer = new Vector2[vertexCount * 2]; // two arrays in one. + } + + /// Step 2 of solving tangents. Fills (part of) a temporary tangent-solution buffer based on the vertices and uvs defined by a submesh's triangle buffer. Only needs to be called once for single-submesh meshes. + /// A temporary Vector3[] for calculating tangents. + /// The mesh's current vertex position buffer. + /// The mesh's current triangles buffer. + /// The mesh's current uvs buffer. + /// Number of vertices that require tangents (or the size of the vertex array) + /// The number of triangle indexes in the triangle array to be used. + internal static void SolveTangents2DTriangles (Vector2[] tempTanBuffer, int[] triangles, int triangleCount, Vector3[] vertices, Vector2[] uvs, int vertexCount) { + Vector2 sdir; + Vector2 tdir; + for (int t = 0; t < triangleCount; t += 3) { + int i1 = triangles[t + 0]; + int i2 = triangles[t + 1]; + int i3 = triangles[t + 2]; + + Vector3 v1 = vertices[i1]; + Vector3 v2 = vertices[i2]; + Vector3 v3 = vertices[i3]; + + Vector2 w1 = uvs[i1]; + Vector2 w2 = uvs[i2]; + Vector2 w3 = uvs[i3]; + + float x1 = v2.x - v1.x; + float x2 = v3.x - v1.x; + float y1 = v2.y - v1.y; + float y2 = v3.y - v1.y; + + float s1 = w2.x - w1.x; + float s2 = w3.x - w1.x; + float t1 = w2.y - w1.y; + float t2 = w3.y - w1.y; + + float div = s1 * t2 - s2 * t1; + float r = (div == 0f) ? 0f : 1f / div; + + sdir.x = (t2 * x1 - t1 * x2) * r; + sdir.y = (t2 * y1 - t1 * y2) * r; + tempTanBuffer[i1] = tempTanBuffer[i2] = tempTanBuffer[i3] = sdir; + + tdir.x = (s1 * x2 - s2 * x1) * r; + tdir.y = (s1 * y2 - s2 * y1) * r; + tempTanBuffer[vertexCount + i1] = tempTanBuffer[vertexCount + i2] = tempTanBuffer[vertexCount + i3] = tdir; + } + } + + /// Step 3 of solving tangents. Fills a Vector4[] tangents array according to values calculated in step 2. + /// A Vector4[] that will eventually be used to set Mesh.tangents + /// A temporary Vector3[] for calculating tangents. + /// Number of vertices that require tangents (or the size of the vertex array) + internal static void SolveTangents2DBuffer (Vector4[] tangents, Vector2[] tempTanBuffer, int vertexCount) { + Vector4 tangent; + tangent.z = 0; + for (int i = 0; i < vertexCount; ++i) { + Vector2 t = tempTanBuffer[i]; + + // t.Normalize() (aggressively inlined). Even better if offloaded to GPU via vertex shader. + float magnitude = Mathf.Sqrt(t.x * t.x + t.y * t.y); + if (magnitude > 1E-05) { + float reciprocalMagnitude = 1f/magnitude; + t.x *= reciprocalMagnitude; + t.y *= reciprocalMagnitude; + } + + Vector2 t2 = tempTanBuffer[vertexCount + i]; + tangent.x = t.x; + tangent.y = t.y; + //tangent.z = 0; + tangent.w = (t.y * t2.x > t.x * t2.y) ? 1 : -1; // 2D direction calculation. Used for binormals. + tangents[i] = tangent; + } + } + #endregion + + #region AttachmentRendering + static List AttachmentVerts = new List(); + static List AttachmentUVs = new List(); + static List AttachmentColors32 = new List(); + static List AttachmentIndices = new List(); + + /// Fills mesh vertex data to render a RegionAttachment. + public static void FillMeshLocal (Mesh mesh, RegionAttachment regionAttachment) { + if (mesh == null) return; + if (regionAttachment == null) return; + + AttachmentVerts.Clear(); + var offsets = regionAttachment.Offset; + AttachmentVerts.Add(new Vector3(offsets[RegionAttachment.BLX], offsets[RegionAttachment.BLY])); + AttachmentVerts.Add(new Vector3(offsets[RegionAttachment.ULX], offsets[RegionAttachment.ULY])); + AttachmentVerts.Add(new Vector3(offsets[RegionAttachment.URX], offsets[RegionAttachment.URY])); + AttachmentVerts.Add(new Vector3(offsets[RegionAttachment.BRX], offsets[RegionAttachment.BRY])); + + AttachmentUVs.Clear(); + var uvs = regionAttachment.UVs; + AttachmentUVs.Add(new Vector2(uvs[RegionAttachment.ULX], uvs[RegionAttachment.ULY])); + AttachmentUVs.Add(new Vector2(uvs[RegionAttachment.URX], uvs[RegionAttachment.URY])); + AttachmentUVs.Add(new Vector2(uvs[RegionAttachment.BRX], uvs[RegionAttachment.BRY])); + AttachmentUVs.Add(new Vector2(uvs[RegionAttachment.BLX], uvs[RegionAttachment.BLY])); + + AttachmentColors32.Clear(); + Color32 c = (Color32)(new Color(regionAttachment.r, regionAttachment.g, regionAttachment.b, regionAttachment.a)); + for (int i = 0; i < 4; i++) + AttachmentColors32.Add(c); + + AttachmentIndices.Clear(); + AttachmentIndices.AddRange(new[] { 0, 2, 1, 0, 3, 2 }); + + mesh.Clear(); + mesh.name = regionAttachment.Name; + mesh.SetVertices(AttachmentVerts); + mesh.SetUVs(0, AttachmentUVs); + mesh.SetColors(AttachmentColors32); + mesh.SetTriangles(AttachmentIndices, 0); + mesh.RecalculateBounds(); + + AttachmentVerts.Clear(); + AttachmentUVs.Clear(); + AttachmentColors32.Clear(); + AttachmentIndices.Clear(); + } + + public static void FillMeshLocal (Mesh mesh, MeshAttachment meshAttachment, SkeletonData skeletonData) { + if (mesh == null) return; + if (meshAttachment == null) return; + int vertexCount = meshAttachment.WorldVerticesLength / 2; + + AttachmentVerts.Clear(); + if (meshAttachment.IsWeighted()) { + int count = meshAttachment.WorldVerticesLength; + int[] meshAttachmentBones = meshAttachment.bones; + int v = 0; + + float[] vertices = meshAttachment.vertices; + for (int w = 0, b = 0; w < count; w += 2) { + float wx = 0, wy = 0; + int n = meshAttachmentBones[v++]; + n += v; + for (; v < n; v++, b += 3) { + BoneMatrix bm = BoneMatrix.CalculateSetupWorld(skeletonData.bones.Items[meshAttachmentBones[v]]); + float vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2]; + wx += (vx * bm.a + vy * bm.b + bm.x) * weight; + wy += (vx * bm.c + vy * bm.d + bm.y) * weight; + } + AttachmentVerts.Add(new Vector3(wx, wy)); + } + } else { + var localVerts = meshAttachment.Vertices; + Vector3 pos = default(Vector3); + for (int i = 0; i < vertexCount; i++) { + int ii = i * 2; + pos.x = localVerts[ii]; + pos.y = localVerts[ii + 1]; + AttachmentVerts.Add(pos); + } + } + + var uvs = meshAttachment.uvs; + Vector2 uv = default(Vector2); + Color32 c = (Color32)(new Color(meshAttachment.r, meshAttachment.g, meshAttachment.b, meshAttachment.a)); + AttachmentUVs.Clear(); + AttachmentColors32.Clear(); + for (int i = 0; i < vertexCount; i++) { + int ii = i * 2; + uv.x = uvs[ii]; + uv.y = uvs[ii + 1]; + AttachmentUVs.Add(uv); + + AttachmentColors32.Add(c); + } + + AttachmentIndices.Clear(); + AttachmentIndices.AddRange(meshAttachment.triangles); + + mesh.Clear(); + mesh.name = meshAttachment.Name; + mesh.SetVertices(AttachmentVerts); + mesh.SetUVs(0, AttachmentUVs); + mesh.SetColors(AttachmentColors32); + mesh.SetTriangles(AttachmentIndices, 0); + mesh.RecalculateBounds(); + + AttachmentVerts.Clear(); + AttachmentUVs.Clear(); + AttachmentColors32.Clear(); + AttachmentIndices.Clear(); + } + #endregion + } +} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs.meta new file mode 100644 index 000000000..712b49275 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 311447d6e56727c4dad7611d5fa5afbf +timeCreated: 1563322425 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshRendererBuffers.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshRendererBuffers.cs new file mode 100644 index 000000000..7494c4231 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshRendererBuffers.cs @@ -0,0 +1,135 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated May 1, 2019. Replaces all prior versions. + * + * Copyright (c) 2013-2019, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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. + *****************************************************************************/ + +// Not for optimization. Do not disable. +#define SPINE_TRIANGLECHECK // Avoid calling SetTriangles at the cost of checking for mesh differences (vertex counts, memberwise attachment list compare) every frame. +//#define SPINE_DEBUG + +using UnityEngine; +using System; +using System.Collections.Generic; + +namespace Spine.Unity { + /// A double-buffered Mesh, and a shared material array, bundled for use by Spine components that need to push a Mesh and materials to a Unity MeshRenderer and MeshFilter. + public class MeshRendererBuffers : IDisposable { + DoubleBuffered doubleBufferedMesh; + internal readonly ExposedList submeshMaterials = new ExposedList(); + internal Material[] sharedMaterials = new Material[0]; + + public void Initialize () { + if (doubleBufferedMesh != null) { + doubleBufferedMesh.GetNext().Clear(); + doubleBufferedMesh.GetNext().Clear(); + submeshMaterials.Clear(); + } else { + doubleBufferedMesh = new DoubleBuffered(); + } + } + + /// Returns a sharedMaterials array for use on a MeshRenderer. + /// + public Material[] GetUpdatedSharedMaterialsArray () { + if (submeshMaterials.Count == sharedMaterials.Length) + submeshMaterials.CopyTo(sharedMaterials); + else + sharedMaterials = submeshMaterials.ToArray(); + + return sharedMaterials; + } + + /// Returns true if the materials were modified since the buffers were last updated. + public bool MaterialsChangedInLastUpdate () { + int newSubmeshMaterials = submeshMaterials.Count; + var sharedMaterials = this.sharedMaterials; + if (newSubmeshMaterials != sharedMaterials.Length) return true; + + var submeshMaterialsItems = submeshMaterials.Items; + for (int i = 0; i < newSubmeshMaterials; i++) + if (!Material.ReferenceEquals(submeshMaterialsItems[i], sharedMaterials[i])) return true; //if (submeshMaterialsItems[i].GetInstanceID() != sharedMaterials[i].GetInstanceID()) return true; + + return false; + } + + /// Updates the internal shared materials array with the given instruction list. + public void UpdateSharedMaterials (ExposedList instructions) { + int newSize = instructions.Count; + { //submeshMaterials.Resize(instructions.Count); + if (newSize > submeshMaterials.Items.Length) + Array.Resize(ref submeshMaterials.Items, newSize); + submeshMaterials.Count = newSize; + } + + var submeshMaterialsItems = submeshMaterials.Items; + var instructionsItems = instructions.Items; + for (int i = 0; i < newSize; i++) + submeshMaterialsItems[i] = instructionsItems[i].material; + } + + public SmartMesh GetNextMesh () { + return doubleBufferedMesh.GetNext(); + } + + public void Clear () { + sharedMaterials = new Material[0]; + submeshMaterials.Clear(); + } + + public void Dispose () { + if (doubleBufferedMesh == null) return; + doubleBufferedMesh.GetNext().Dispose(); + doubleBufferedMesh.GetNext().Dispose(); + doubleBufferedMesh = null; + } + + ///This is a Mesh that also stores the instructions SkeletonRenderer generated for it. + public class SmartMesh : IDisposable { + public Mesh mesh = SpineMesh.NewSkeletonMesh(); + public SkeletonRendererInstruction instructionUsed = new SkeletonRendererInstruction(); + + public void Clear () { + mesh.Clear(); + instructionUsed.Clear(); + } + + public void Dispose () { + if (mesh != null) { + #if UNITY_EDITOR + if (Application.isEditor && !Application.isPlaying) + UnityEngine.Object.DestroyImmediate(mesh); + else + UnityEngine.Object.Destroy(mesh); + #else + UnityEngine.Object.Destroy(mesh); + #endif + } + mesh = null; + } + } + } +} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshRendererBuffers.cs.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshRendererBuffers.cs.meta new file mode 100644 index 000000000..3e4c8a1b2 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshRendererBuffers.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b1ab80744ac17724dbc0d15fdb6f4727 +timeCreated: 1563322425 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SkeletonRendererInstruction.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SkeletonRendererInstruction.cs new file mode 100644 index 000000000..3657834a4 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SkeletonRendererInstruction.cs @@ -0,0 +1,178 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated May 1, 2019. Replaces all prior versions. + * + * Copyright (c) 2013-2019, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "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 LLC 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. + *****************************************************************************/ + +// Not for optimization. Do not disable. +#define SPINE_TRIANGLECHECK // Avoid calling SetTriangles at the cost of checking for mesh differences (vertex counts, memberwise attachment list compare) every frame. +//#define SPINE_DEBUG + +using UnityEngine; +using System; +using System.Collections.Generic; + +namespace Spine.Unity { + /// Instructions used by a SkeletonRenderer to render a mesh. + public class SkeletonRendererInstruction { + public readonly ExposedList submeshInstructions = new ExposedList(); + + public bool immutableTriangles; + #if SPINE_TRIANGLECHECK + public bool hasActiveClipping; + public int rawVertexCount = -1; + public readonly ExposedList attachments = new ExposedList(); + #endif + + public void Clear () { + #if SPINE_TRIANGLECHECK + this.attachments.Clear(false); + rawVertexCount = -1; + hasActiveClipping = false; + #endif + this.submeshInstructions.Clear(false); + } + + public void Dispose () { + attachments.Clear(true); + } + + public void SetWithSubset (ExposedList instructions, int startSubmesh, int endSubmesh) { + #if SPINE_TRIANGLECHECK + int runningVertexCount = 0; + #endif + + var submeshes = this.submeshInstructions; + submeshes.Clear(false); + int submeshCount = endSubmesh - startSubmesh; + submeshes.Resize(submeshCount); + var submeshesItems = submeshes.Items; + var instructionsItems = instructions.Items; + for (int i = 0; i < submeshCount; i++) { + var instruction = instructionsItems[startSubmesh + i]; + submeshesItems[i] = instruction; + #if SPINE_TRIANGLECHECK + this.hasActiveClipping |= instruction.hasClipping; + submeshesItems[i].rawFirstVertexIndex = runningVertexCount; // Ensure current instructions have correct cached values. + runningVertexCount += instruction.rawVertexCount; // vertexCount will also be used for the rest of this method. + #endif + } + #if SPINE_TRIANGLECHECK + this.rawVertexCount = runningVertexCount; + + // assumption: instructions are contiguous. start and end are valid within instructions. + + int startSlot = instructionsItems[startSubmesh].startSlot; + int endSlot = instructionsItems[endSubmesh - 1].endSlot; + attachments.Clear(false); + int attachmentCount = endSlot - startSlot; + attachments.Resize(attachmentCount); + var attachmentsItems = attachments.Items; + + var drawOrderItems = instructionsItems[0].skeleton.drawOrder.Items; + for (int i = 0; i < attachmentCount; i++) { + Slot slot = drawOrderItems[startSlot + i]; + if (!slot.bone.active) continue; + attachmentsItems[i] = slot.attachment; + } + + #endif + } + + public void Set (SkeletonRendererInstruction other) { + this.immutableTriangles = other.immutableTriangles; + + #if SPINE_TRIANGLECHECK + this.hasActiveClipping = other.hasActiveClipping; + this.rawVertexCount = other.rawVertexCount; + this.attachments.Clear(false); + this.attachments.EnsureCapacity(other.attachments.Capacity); + this.attachments.Count = other.attachments.Count; + other.attachments.CopyTo(this.attachments.Items); + #endif + + this.submeshInstructions.Clear(false); + this.submeshInstructions.EnsureCapacity(other.submeshInstructions.Capacity); + this.submeshInstructions.Count = other.submeshInstructions.Count; + other.submeshInstructions.CopyTo(this.submeshInstructions.Items); + } + + public static bool GeometryNotEqual (SkeletonRendererInstruction a, SkeletonRendererInstruction b) { + #if SPINE_TRIANGLECHECK + #if UNITY_EDITOR + if (!Application.isPlaying) + return true; + #endif + + if (a.hasActiveClipping || b.hasActiveClipping) return true; // Triangles are unpredictable when clipping is active. + + // Everything below assumes the raw vertex and triangle counts were used. (ie, no clipping was done) + if (a.rawVertexCount != b.rawVertexCount) return true; + + if (a.immutableTriangles != b.immutableTriangles) return true; + + int attachmentCountB = b.attachments.Count; + if (a.attachments.Count != attachmentCountB) return true; // Bounds check for the looped storedAttachments count below. + + // Submesh count changed + int submeshCountA = a.submeshInstructions.Count; + int submeshCountB = b.submeshInstructions.Count; + if (submeshCountA != submeshCountB) return true; + + // Submesh Instruction mismatch + var submeshInstructionsItemsA = a.submeshInstructions.Items; + var submeshInstructionsItemsB = b.submeshInstructions.Items; + + var attachmentsA = a.attachments.Items; + var attachmentsB = b.attachments.Items; + for (int i = 0; i < attachmentCountB; i++) + if (!System.Object.ReferenceEquals(attachmentsA[i], attachmentsB[i])) return true; + + for (int i = 0; i < submeshCountB; i++) { + var submeshA = submeshInstructionsItemsA[i]; + var submeshB = submeshInstructionsItemsB[i]; + + if (!( + submeshA.rawVertexCount == submeshB.rawVertexCount && + submeshA.startSlot == submeshB.startSlot && + submeshA.endSlot == submeshB.endSlot + && submeshA.rawTriangleCount == submeshB.rawTriangleCount && + submeshA.rawFirstVertexIndex == submeshB.rawFirstVertexIndex + )) + return true; + } + + return false; + #else + // In normal immutable triangle use, immutableTriangles will be initially false, forcing the smartmesh to update the first time but never again after that, unless there was an immutableTriangles flag mismatch.. + if (a.immutableTriangles || b.immutableTriangles) + return (a.immutableTriangles != b.immutableTriangles); + + return true; + #endif + } + } +} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SkeletonRendererInstruction.cs.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SkeletonRendererInstruction.cs.meta new file mode 100644 index 000000000..50a8a97d8 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SkeletonRendererInstruction.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d07866ade25bd0b44a7bb1d59bacf4cb +timeCreated: 1563322425 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs index 85e7dfe47..fc0935440 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/SpineMesh.cs @@ -80,1492 +80,5 @@ namespace Spine.Unity { preActiveClippingSlotSource ); } - } - - public delegate void MeshGeneratorDelegate (MeshGeneratorBuffers buffers); - public struct MeshGeneratorBuffers { - /// The vertex count that will actually be used for the mesh. The Lengths of the buffer arrays may be larger than this number. - public int vertexCount; - - /// Vertex positions. To be used for UnityEngine.Mesh.vertices. - public Vector3[] vertexBuffer; - - /// Vertex UVs. To be used for UnityEngine.Mesh.uvs. - public Vector2[] uvBuffer; - - /// Vertex colors. To be used for UnityEngine.Mesh.colors32. - public Color32[] colorBuffer; - - /// The Spine rendering component's MeshGenerator. - public MeshGenerator meshGenerator; - } - - /// Holds several methods to prepare and generate a UnityEngine mesh based on a skeleton. Contains buffers needed to perform the operation, and serializes settings for mesh generation. - [System.Serializable] - public class MeshGenerator { - public Settings settings = Settings.Default; - - [System.Serializable] - public struct Settings { - public bool useClipping; - [Space] - [Range(-0.1f, 0f)] public float zSpacing; - [Space] - [Header("Vertex Data")] - public bool pmaVertexColors; - public bool tintBlack; - public bool calculateTangents; - public bool addNormals; - public bool immutableTriangles; - - static public Settings Default { - get { - return new Settings { - pmaVertexColors = true, - zSpacing = 0f, - useClipping = true, - tintBlack = false, - calculateTangents = false, - //renderMeshes = true, - addNormals = false, - immutableTriangles = false - }; - } - } - } - - const float BoundsMinDefault = float.PositiveInfinity; - const float BoundsMaxDefault = float.NegativeInfinity; - - [NonSerialized] readonly ExposedList vertexBuffer = new ExposedList(4); - [NonSerialized] readonly ExposedList uvBuffer = new ExposedList(4); - [NonSerialized] readonly ExposedList colorBuffer = new ExposedList(4); - [NonSerialized] readonly ExposedList> submeshes = new ExposedList> { new ExposedList(6) }; // start with 1 submesh. - - [NonSerialized] Vector2 meshBoundsMin, meshBoundsMax; - [NonSerialized] float meshBoundsThickness; - [NonSerialized] int submeshIndex = 0; - - [NonSerialized] SkeletonClipping clipper = new SkeletonClipping(); - [NonSerialized] float[] tempVerts = new float[8]; - [NonSerialized] int[] regionTriangles = { 0, 1, 2, 2, 3, 0 }; - - #region Optional Buffers - // These optional buffers are lazy-instantiated when the feature is used. - [NonSerialized] Vector3[] normals; - [NonSerialized] Vector4[] tangents; - [NonSerialized] Vector2[] tempTanBuffer; - [NonSerialized] ExposedList uv2; - [NonSerialized] ExposedList uv3; - #endregion - - public int VertexCount { get { return vertexBuffer.Count; } } - - /// A set of mesh arrays whose values are modifiable by the user. Modify these values before they are passed to the UnityEngine mesh object in order to see the effect. - public MeshGeneratorBuffers Buffers { - get { - return new MeshGeneratorBuffers { - vertexCount = this.VertexCount, - vertexBuffer = this.vertexBuffer.Items, - uvBuffer = this.uvBuffer.Items, - colorBuffer = this.colorBuffer.Items, - meshGenerator = this - }; - } - } - - public MeshGenerator () { - submeshes.TrimExcess(); - } - - #region Step 1 : Generate Instructions - public static void GenerateSingleSubmeshInstruction (SkeletonRendererInstruction instructionOutput, Skeleton skeleton, Material material) { - ExposedList drawOrder = skeleton.drawOrder; - int drawOrderCount = drawOrder.Count; - - // Clear last state of attachments and submeshes - instructionOutput.Clear(); // submeshInstructions.Clear(); attachments.Clear(); - var workingSubmeshInstructions = instructionOutput.submeshInstructions; - workingSubmeshInstructions.Resize(1); - - #if SPINE_TRIANGLECHECK - instructionOutput.attachments.Resize(drawOrderCount); - var workingAttachmentsItems = instructionOutput.attachments.Items; - int totalRawVertexCount = 0; - #endif - - var current = new SubmeshInstruction { - skeleton = skeleton, - preActiveClippingSlotSource = -1, - startSlot = 0, - #if SPINE_TRIANGLECHECK - rawFirstVertexIndex = 0, - #endif - material = material, - forceSeparate = false, - endSlot = drawOrderCount - }; - - #if SPINE_TRIANGLECHECK - bool skeletonHasClipping = false; - var drawOrderItems = drawOrder.Items; - for (int i = 0; i < drawOrderCount; i++) { - Slot slot = drawOrderItems[i]; - if (!slot.bone.active) continue; - Attachment attachment = slot.attachment; - - workingAttachmentsItems[i] = attachment; - int attachmentTriangleCount; - int attachmentVertexCount; - - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - attachmentVertexCount = 4; - attachmentTriangleCount = 6; - } else { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - attachmentVertexCount = meshAttachment.worldVerticesLength >> 1; - attachmentTriangleCount = meshAttachment.triangles.Length; - } else { - var clippingAttachment = attachment as ClippingAttachment; - if (clippingAttachment != null) { - current.hasClipping = true; - skeletonHasClipping = true; - } - attachmentVertexCount = 0; - attachmentTriangleCount = 0; - } - } - current.rawTriangleCount += attachmentTriangleCount; - current.rawVertexCount += attachmentVertexCount; - totalRawVertexCount += attachmentVertexCount; - - } - - instructionOutput.hasActiveClipping = skeletonHasClipping; - instructionOutput.rawVertexCount = totalRawVertexCount; - #endif - - workingSubmeshInstructions.Items[0] = current; - } - - public static void GenerateSkeletonRendererInstruction (SkeletonRendererInstruction instructionOutput, Skeleton skeleton, Dictionary customSlotMaterials, List separatorSlots, bool generateMeshOverride, bool immutableTriangles = false) { - // if (skeleton == null) throw new ArgumentNullException("skeleton"); - // if (instructionOutput == null) throw new ArgumentNullException("instructionOutput"); - - ExposedList drawOrder = skeleton.drawOrder; - int drawOrderCount = drawOrder.Count; - - // Clear last state of attachments and submeshes - instructionOutput.Clear(); // submeshInstructions.Clear(); attachments.Clear(); - var workingSubmeshInstructions = instructionOutput.submeshInstructions; - #if SPINE_TRIANGLECHECK - instructionOutput.attachments.Resize(drawOrderCount); - var workingAttachmentsItems = instructionOutput.attachments.Items; - int totalRawVertexCount = 0; - bool skeletonHasClipping = false; - #endif - - var current = new SubmeshInstruction { - skeleton = skeleton, - preActiveClippingSlotSource = -1 - }; - - #if !SPINE_TK2D - bool isCustomSlotMaterialsPopulated = customSlotMaterials != null && customSlotMaterials.Count > 0; - #endif - - int separatorCount = separatorSlots == null ? 0 : separatorSlots.Count; - bool hasSeparators = separatorCount > 0; - - int clippingAttachmentSource = -1; - int lastPreActiveClipping = -1; // The index of the last slot that had an active ClippingAttachment. - SlotData clippingEndSlot = null; - int submeshIndex = 0; - var drawOrderItems = drawOrder.Items; - for (int i = 0; i < drawOrderCount; i++) { - Slot slot = drawOrderItems[i]; - if (!slot.bone.active) continue; - Attachment attachment = slot.attachment; - #if SPINE_TRIANGLECHECK - workingAttachmentsItems[i] = attachment; - int attachmentVertexCount = 0, attachmentTriangleCount = 0; - #endif - - object rendererObject = null; // An AtlasRegion in plain Spine-Unity. Spine-TK2D hooks into TK2D's system. eventual source of Material object. - bool noRender = false; // Using this allows empty slots as separators, and keeps separated parts more stable despite slots being reordered - - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - rendererObject = regionAttachment.RendererObject; - #if SPINE_TRIANGLECHECK - attachmentVertexCount = 4; - attachmentTriangleCount = 6; - #endif - } else { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - rendererObject = meshAttachment.RendererObject; - #if SPINE_TRIANGLECHECK - attachmentVertexCount = meshAttachment.worldVerticesLength >> 1; - attachmentTriangleCount = meshAttachment.triangles.Length; - #endif - } else { - #if SPINE_TRIANGLECHECK - var clippingAttachment = attachment as ClippingAttachment; - if (clippingAttachment != null) { - clippingEndSlot = clippingAttachment.endSlot; - clippingAttachmentSource = i; - current.hasClipping = true; - skeletonHasClipping = true; - } - #endif - noRender = true; - } - } - - if (clippingEndSlot != null && slot.data == clippingEndSlot && i != clippingAttachmentSource) { - clippingEndSlot = null; - clippingAttachmentSource = -1; - } - - // Create a new SubmeshInstruction when material changes. (or when forced to separate by a submeshSeparator) - // Slot with a separator/new material will become the starting slot of the next new instruction. - if (hasSeparators) { //current.forceSeparate = hasSeparators && separatorSlots.Contains(slot); - current.forceSeparate = false; - for (int s = 0; s < separatorCount; s++) { - if (Slot.ReferenceEquals(slot, separatorSlots[s])) { - current.forceSeparate = true; - break; - } - } - } - - if (noRender) { - if (current.forceSeparate && generateMeshOverride) { // && current.rawVertexCount > 0) { - { // Add - current.endSlot = i; - current.preActiveClippingSlotSource = lastPreActiveClipping; - - workingSubmeshInstructions.Resize(submeshIndex + 1); - workingSubmeshInstructions.Items[submeshIndex] = current; - - submeshIndex++; - } - - current.startSlot = i; - lastPreActiveClipping = clippingAttachmentSource; - #if SPINE_TRIANGLECHECK - current.rawTriangleCount = 0; - current.rawVertexCount = 0; - current.rawFirstVertexIndex = totalRawVertexCount; - current.hasClipping = clippingAttachmentSource >= 0; - #endif - } - } else { - #if !SPINE_TK2D - Material material; - if (isCustomSlotMaterialsPopulated) { - if (!customSlotMaterials.TryGetValue(slot, out material)) - material = (Material)((AtlasRegion)rendererObject).page.rendererObject; - } else { - material = (Material)((AtlasRegion)rendererObject).page.rendererObject; - } - #else - Material material = (rendererObject is Material) ? (Material)rendererObject : (Material)((AtlasRegion)rendererObject).page.rendererObject; - #endif - - if (current.forceSeparate || (current.rawVertexCount > 0 && !System.Object.ReferenceEquals(current.material, material))) { // Material changed. Add the previous submesh. - { // Add - current.endSlot = i; - current.preActiveClippingSlotSource = lastPreActiveClipping; - - workingSubmeshInstructions.Resize(submeshIndex + 1); - workingSubmeshInstructions.Items[submeshIndex] = current; - submeshIndex++; - } - current.startSlot = i; - lastPreActiveClipping = clippingAttachmentSource; - #if SPINE_TRIANGLECHECK - current.rawTriangleCount = 0; - current.rawVertexCount = 0; - current.rawFirstVertexIndex = totalRawVertexCount; - current.hasClipping = clippingAttachmentSource >= 0; - #endif - } - - // Update state for the next Attachment. - current.material = material; - #if SPINE_TRIANGLECHECK - current.rawTriangleCount += attachmentTriangleCount; - current.rawVertexCount += attachmentVertexCount; - current.rawFirstVertexIndex = totalRawVertexCount; - totalRawVertexCount += attachmentVertexCount; - #endif - } - } - - if (current.rawVertexCount > 0) { - { // Add last or only submesh. - current.endSlot = drawOrderCount; - current.preActiveClippingSlotSource = lastPreActiveClipping; - current.forceSeparate = false; - - workingSubmeshInstructions.Resize(submeshIndex + 1); - workingSubmeshInstructions.Items[submeshIndex] = current; - //submeshIndex++; - } - } - - #if SPINE_TRIANGLECHECK - instructionOutput.hasActiveClipping = skeletonHasClipping; - instructionOutput.rawVertexCount = totalRawVertexCount; - #endif - instructionOutput.immutableTriangles = immutableTriangles; - } - - public static void TryReplaceMaterials (ExposedList workingSubmeshInstructions, Dictionary customMaterialOverride) { - // Material overrides are done here so they can be applied per submesh instead of per slot - // but they will still be passed through the GenerateMeshOverride delegate, - // and will still go through the normal material match check step in STEP 3. - var wsii = workingSubmeshInstructions.Items; - for (int i = 0; i < workingSubmeshInstructions.Count; i++) { - var m = wsii[i].material; - Material mo; - if (customMaterialOverride.TryGetValue(m, out mo)) - wsii[i].material = mo; - } - } - #endregion - - #region Step 2 : Populate vertex data and triangle index buffers. - public void Begin () { - vertexBuffer.Clear(false); - colorBuffer.Clear(false); - uvBuffer.Clear(false); - clipper.ClipEnd(); - - { - meshBoundsMin.x = BoundsMinDefault; - meshBoundsMin.y = BoundsMinDefault; - meshBoundsMax.x = BoundsMaxDefault; - meshBoundsMax.y = BoundsMaxDefault; - meshBoundsThickness = 0f; - } - - submeshIndex = 0; - submeshes.Count = 1; - //submeshes.Items[0].Clear(false); - } - - public void AddSubmesh (SubmeshInstruction instruction, bool updateTriangles = true) { - var settings = this.settings; - - int newSubmeshCount = submeshIndex + 1; - if (submeshes.Items.Length < newSubmeshCount) - submeshes.Resize(newSubmeshCount); - submeshes.Count = newSubmeshCount; - var submesh = submeshes.Items[submeshIndex]; - if (submesh == null) - submeshes.Items[submeshIndex] = submesh = new ExposedList(); - submesh.Clear(false); - - var skeleton = instruction.skeleton; - var drawOrderItems = skeleton.drawOrder.Items; - - Color32 color = default(Color32); - float skeletonA = skeleton.a * 255, skeletonR = skeleton.r, skeletonG = skeleton.g, skeletonB = skeleton.b; - Vector2 meshBoundsMin = this.meshBoundsMin, meshBoundsMax = this.meshBoundsMax; - - // Settings - float zSpacing = settings.zSpacing; - bool pmaVertexColors = settings.pmaVertexColors; - bool tintBlack = settings.tintBlack; - #if SPINE_TRIANGLECHECK - bool useClipping = settings.useClipping && instruction.hasClipping; - #else - bool useClipping = settings.useClipping; - #endif - - if (useClipping) { - if (instruction.preActiveClippingSlotSource >= 0) { - var slot = drawOrderItems[instruction.preActiveClippingSlotSource]; - clipper.ClipStart(slot, slot.attachment as ClippingAttachment); - } - } - - for (int slotIndex = instruction.startSlot; slotIndex < instruction.endSlot; slotIndex++) { - var slot = drawOrderItems[slotIndex]; - if (!slot.bone.active) continue; - var attachment = slot.attachment; - float z = zSpacing * slotIndex; - - var workingVerts = this.tempVerts; - float[] uvs; - int[] attachmentTriangleIndices; - int attachmentVertexCount; - int attachmentIndexCount; - - Color c = default(Color); - - // Identify and prepare values. - var region = attachment as RegionAttachment; - if (region != null) { - region.ComputeWorldVertices(slot.bone, workingVerts, 0); - uvs = region.uvs; - attachmentTriangleIndices = regionTriangles; - c.r = region.r; c.g = region.g; c.b = region.b; c.a = region.a; - attachmentVertexCount = 4; - attachmentIndexCount = 6; - } else { - var mesh = attachment as MeshAttachment; - if (mesh != null) { - int meshVerticesLength = mesh.worldVerticesLength; - if (workingVerts.Length < meshVerticesLength) { - workingVerts = new float[meshVerticesLength]; - this.tempVerts = workingVerts; - } - mesh.ComputeWorldVertices(slot, 0, meshVerticesLength, workingVerts, 0); //meshAttachment.ComputeWorldVertices(slot, tempVerts); - uvs = mesh.uvs; - attachmentTriangleIndices = mesh.triangles; - c.r = mesh.r; c.g = mesh.g; c.b = mesh.b; c.a = mesh.a; - attachmentVertexCount = meshVerticesLength >> 1; // meshVertexCount / 2; - attachmentIndexCount = mesh.triangles.Length; - } else { - if (useClipping) { - var clippingAttachment = attachment as ClippingAttachment; - if (clippingAttachment != null) { - clipper.ClipStart(slot, clippingAttachment); - continue; - } - } - - // If not any renderable attachment. - clipper.ClipEnd(slot); - continue; - } - } - - if (pmaVertexColors) { - color.a = (byte)(skeletonA * slot.a * c.a); - color.r = (byte)(skeletonR * slot.r * c.r * color.a); - color.g = (byte)(skeletonG * slot.g * c.g * color.a); - color.b = (byte)(skeletonB * slot.b * c.b * color.a); - if (slot.data.blendMode == BlendMode.Additive) color.a = 0; - } else { - color.a = (byte)(skeletonA * slot.a * c.a); - color.r = (byte)(skeletonR * slot.r * c.r * 255); - color.g = (byte)(skeletonG * slot.g * c.g * 255); - color.b = (byte)(skeletonB * slot.b * c.b * 255); - } - - if (useClipping && clipper.IsClipping) { - clipper.ClipTriangles(workingVerts, attachmentVertexCount << 1, attachmentTriangleIndices, attachmentIndexCount, uvs); - workingVerts = clipper.clippedVertices.Items; - attachmentVertexCount = clipper.clippedVertices.Count >> 1; - attachmentTriangleIndices = clipper.clippedTriangles.Items; - attachmentIndexCount = clipper.clippedTriangles.Count; - uvs = clipper.clippedUVs.Items; - } - - // Actually add slot/attachment data into buffers. - if (attachmentVertexCount != 0 && attachmentIndexCount != 0) { - if (tintBlack) - AddAttachmentTintBlack(slot.r2, slot.g2, slot.b2, attachmentVertexCount); - - //AddAttachment(workingVerts, uvs, color, attachmentTriangleIndices, attachmentVertexCount, attachmentIndexCount, ref meshBoundsMin, ref meshBoundsMax, z); - int ovc = vertexBuffer.Count; - // Add data to vertex buffers - { - int newVertexCount = ovc + attachmentVertexCount; - int oldArraySize = vertexBuffer.Items.Length; - if (newVertexCount > oldArraySize) { - int newArraySize = (int)(oldArraySize * 1.3f); - if (newArraySize < newVertexCount) newArraySize = newVertexCount; - Array.Resize(ref vertexBuffer.Items, newArraySize); - Array.Resize(ref uvBuffer.Items, newArraySize); - Array.Resize(ref colorBuffer.Items, newArraySize); - } - vertexBuffer.Count = uvBuffer.Count = colorBuffer.Count = newVertexCount; - } - - var vbi = vertexBuffer.Items; - var ubi = uvBuffer.Items; - var cbi = colorBuffer.Items; - if (ovc == 0) { - for (int i = 0; i < attachmentVertexCount; i++) { - int vi = ovc + i; - int i2 = i << 1; // i * 2 - float x = workingVerts[i2]; - float y = workingVerts[i2 + 1]; - - vbi[vi].x = x; - vbi[vi].y = y; - vbi[vi].z = z; - ubi[vi].x = uvs[i2]; - ubi[vi].y = uvs[i2 + 1]; - cbi[vi] = color; - - // Calculate bounds. - if (x < meshBoundsMin.x) meshBoundsMin.x = x; - if (x > meshBoundsMax.x) meshBoundsMax.x = x; - if (y < meshBoundsMin.y) meshBoundsMin.y = y; - if (y > meshBoundsMax.y) meshBoundsMax.y = y; - } - } else { - for (int i = 0; i < attachmentVertexCount; i++) { - int vi = ovc + i; - int i2 = i << 1; // i * 2 - float x = workingVerts[i2]; - float y = workingVerts[i2 + 1]; - - vbi[vi].x = x; - vbi[vi].y = y; - vbi[vi].z = z; - ubi[vi].x = uvs[i2]; - ubi[vi].y = uvs[i2 + 1]; - cbi[vi] = color; - - // Calculate bounds. - if (x < meshBoundsMin.x) meshBoundsMin.x = x; - else if (x > meshBoundsMax.x) meshBoundsMax.x = x; - if (y < meshBoundsMin.y) meshBoundsMin.y = y; - else if (y > meshBoundsMax.y) meshBoundsMax.y = y; - } - } - - - // Add data to triangle buffer - if (updateTriangles) { - int oldTriangleCount = submesh.Count; - { //submesh.Resize(oldTriangleCount + attachmentIndexCount); - int newTriangleCount = oldTriangleCount + attachmentIndexCount; - if (newTriangleCount > submesh.Items.Length) Array.Resize(ref submesh.Items, newTriangleCount); - submesh.Count = newTriangleCount; - } - var submeshItems = submesh.Items; - for (int i = 0; i < attachmentIndexCount; i++) - submeshItems[oldTriangleCount + i] = attachmentTriangleIndices[i] + ovc; - } - } - - clipper.ClipEnd(slot); - } - clipper.ClipEnd(); - - this.meshBoundsMin = meshBoundsMin; - this.meshBoundsMax = meshBoundsMax; - meshBoundsThickness = instruction.endSlot * zSpacing; - - // Trim or zero submesh triangles. - var currentSubmeshItems = submesh.Items; - for (int i = submesh.Count, n = currentSubmeshItems.Length; i < n; i++) - currentSubmeshItems[i] = 0; - - submeshIndex++; // Next AddSubmesh will use a new submeshIndex value. - } - - public void BuildMesh (SkeletonRendererInstruction instruction, bool updateTriangles) { - var wsii = instruction.submeshInstructions.Items; - for (int i = 0, n = instruction.submeshInstructions.Count; i < n; i++) - this.AddSubmesh(wsii[i], updateTriangles); - } - - // Use this faster method when no clipping is involved. - public void BuildMeshWithArrays (SkeletonRendererInstruction instruction, bool updateTriangles) { - var settings = this.settings; - int totalVertexCount = instruction.rawVertexCount; - - // Add data to vertex buffers - { - if (totalVertexCount > vertexBuffer.Items.Length) { // Manual ExposedList.Resize() - Array.Resize(ref vertexBuffer.Items, totalVertexCount); - Array.Resize(ref uvBuffer.Items, totalVertexCount); - Array.Resize(ref colorBuffer.Items, totalVertexCount); - } - vertexBuffer.Count = uvBuffer.Count = colorBuffer.Count = totalVertexCount; - } - - // Populate Verts - Color32 color = default(Color32); - - int vertexIndex = 0; - var tempVerts = this.tempVerts; - Vector2 bmin = this.meshBoundsMin; - Vector2 bmax = this.meshBoundsMax; - - var vbi = vertexBuffer.Items; - var ubi = uvBuffer.Items; - var cbi = colorBuffer.Items; - int lastSlotIndex = 0; - - // drawOrder[endSlot] is excluded - for (int si = 0, n = instruction.submeshInstructions.Count; si < n; si++) { - var submesh = instruction.submeshInstructions.Items[si]; - var skeleton = submesh.skeleton; - var drawOrderItems = skeleton.drawOrder.Items; - float a = skeleton.a * 255, r = skeleton.r, g = skeleton.g, b = skeleton.b; - - int endSlot = submesh.endSlot; - int startSlot = submesh.startSlot; - lastSlotIndex = endSlot; - - if (settings.tintBlack) { - Vector2 rg, b2; - int vi = vertexIndex; - b2.y = 1f; - - { - if (uv2 == null) { - uv2 = new ExposedList(); - uv3 = new ExposedList(); - } - if (totalVertexCount > uv2.Items.Length) { // Manual ExposedList.Resize() - Array.Resize(ref uv2.Items, totalVertexCount); - Array.Resize(ref uv3.Items, totalVertexCount); - } - uv2.Count = uv3.Count = totalVertexCount; - } - - var uv2i = uv2.Items; - var uv3i = uv3.Items; - - for (int slotIndex = startSlot; slotIndex < endSlot; slotIndex++) { - var slot = drawOrderItems[slotIndex]; - if (!slot.bone.active) continue; - var attachment = slot.attachment; - - rg.x = slot.r2; //r - rg.y = slot.g2; //g - b2.x = slot.b2; //b - - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - uv2i[vi] = rg; uv2i[vi + 1] = rg; uv2i[vi + 2] = rg; uv2i[vi + 3] = rg; - uv3i[vi] = b2; uv3i[vi + 1] = b2; uv3i[vi + 2] = b2; uv3i[vi + 3] = b2; - vi += 4; - } else { //} if (settings.renderMeshes) { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - int meshVertexCount = meshAttachment.worldVerticesLength; - for (int iii = 0; iii < meshVertexCount; iii += 2) { - uv2i[vi] = rg; - uv3i[vi] = b2; - vi++; - } - } - } - } - } - - for (int slotIndex = startSlot; slotIndex < endSlot; slotIndex++) { - var slot = drawOrderItems[slotIndex]; - if (!slot.bone.active) continue; - var attachment = slot.attachment; - float z = slotIndex * settings.zSpacing; - - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - regionAttachment.ComputeWorldVertices(slot.bone, tempVerts, 0); - - float x1 = tempVerts[RegionAttachment.BLX], y1 = tempVerts[RegionAttachment.BLY]; - float x2 = tempVerts[RegionAttachment.ULX], y2 = tempVerts[RegionAttachment.ULY]; - float x3 = tempVerts[RegionAttachment.URX], y3 = tempVerts[RegionAttachment.URY]; - float x4 = tempVerts[RegionAttachment.BRX], y4 = tempVerts[RegionAttachment.BRY]; - vbi[vertexIndex].x = x1; vbi[vertexIndex].y = y1; vbi[vertexIndex].z = z; - vbi[vertexIndex + 1].x = x4; vbi[vertexIndex + 1].y = y4; vbi[vertexIndex + 1].z = z; - vbi[vertexIndex + 2].x = x2; vbi[vertexIndex + 2].y = y2; vbi[vertexIndex + 2].z = z; - vbi[vertexIndex + 3].x = x3; vbi[vertexIndex + 3].y = y3; vbi[vertexIndex + 3].z = z; - - if (settings.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); - } - - cbi[vertexIndex] = color; cbi[vertexIndex + 1] = color; cbi[vertexIndex + 2] = color; cbi[vertexIndex + 3] = color; - - float[] regionUVs = regionAttachment.uvs; - ubi[vertexIndex].x = regionUVs[RegionAttachment.BLX]; ubi[vertexIndex].y = regionUVs[RegionAttachment.BLY]; - ubi[vertexIndex + 1].x = regionUVs[RegionAttachment.BRX]; ubi[vertexIndex + 1].y = regionUVs[RegionAttachment.BRY]; - ubi[vertexIndex + 2].x = regionUVs[RegionAttachment.ULX]; ubi[vertexIndex + 2].y = regionUVs[RegionAttachment.ULY]; - ubi[vertexIndex + 3].x = regionUVs[RegionAttachment.URX]; ubi[vertexIndex + 3].y = regionUVs[RegionAttachment.URY]; - - if (x1 < bmin.x) bmin.x = x1; // Potential first attachment bounds initialization. Initial min should not block initial max. Same for Y below. - if (x1 > bmax.x) bmax.x = x1; - if (x2 < bmin.x) bmin.x = x2; - else if (x2 > bmax.x) bmax.x = x2; - if (x3 < bmin.x) bmin.x = x3; - else if (x3 > bmax.x) bmax.x = x3; - if (x4 < bmin.x) bmin.x = x4; - else if (x4 > bmax.x) bmax.x = x4; - - if (y1 < bmin.y) bmin.y = y1; - if (y1 > bmax.y) bmax.y = y1; - if (y2 < bmin.y) bmin.y = y2; - else if (y2 > bmax.y) bmax.y = y2; - if (y3 < bmin.y) bmin.y = y3; - else if (y3 > bmax.y) bmax.y = y3; - if (y4 < bmin.y) bmin.y = y4; - else if (y4 > bmax.y) bmax.y = y4; - - vertexIndex += 4; - } else { //if (settings.renderMeshes) { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - int meshVertexCount = meshAttachment.worldVerticesLength; - if (tempVerts.Length < meshVertexCount) this.tempVerts = tempVerts = new float[meshVertexCount]; - meshAttachment.ComputeWorldVertices(slot, tempVerts); - - if (settings.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[] attachmentUVs = meshAttachment.uvs; - - // Potential first attachment bounds initialization. See conditions in RegionAttachment logic. - if (vertexIndex == 0) { - // Initial min should not block initial max. - // vi == vertexIndex does not always mean the bounds are fresh. It could be a submesh. Do not nuke old values by omitting the check. - // Should know that this is the first attachment in the submesh. slotIndex == startSlot could be an empty slot. - float fx = tempVerts[0], fy = tempVerts[1]; - if (fx < bmin.x) bmin.x = fx; - if (fx > bmax.x) bmax.x = fx; - if (fy < bmin.y) bmin.y = fy; - if (fy > bmax.y) bmax.y = fy; - } - - for (int iii = 0; iii < meshVertexCount; iii += 2) { - float x = tempVerts[iii], y = tempVerts[iii + 1]; - vbi[vertexIndex].x = x; vbi[vertexIndex].y = y; vbi[vertexIndex].z = z; - cbi[vertexIndex] = color; ubi[vertexIndex].x = attachmentUVs[iii]; ubi[vertexIndex].y = attachmentUVs[iii + 1]; - - if (x < bmin.x) bmin.x = x; - else if (x > bmax.x) bmax.x = x; - - if (y < bmin.y) bmin.y = y; - else if (y > bmax.y) bmax.y = y; - - vertexIndex++; - } - } - } - } - } - - this.meshBoundsMin = bmin; - this.meshBoundsMax = bmax; - this.meshBoundsThickness = lastSlotIndex * settings.zSpacing; - - int submeshInstructionCount = instruction.submeshInstructions.Count; - submeshes.Count = submeshInstructionCount; - - // Add triangles - if (updateTriangles) { - // Match submesh buffers count with submeshInstruction count. - if (this.submeshes.Items.Length < submeshInstructionCount) { - this.submeshes.Resize(submeshInstructionCount); - for (int i = 0, n = submeshInstructionCount; i < n; i++) { - var submeshBuffer = this.submeshes.Items[i]; - if (submeshBuffer == null) - this.submeshes.Items[i] = new ExposedList(); - else - submeshBuffer.Clear(false); - } - } - - var submeshInstructionsItems = instruction.submeshInstructions.Items; // This relies on the resize above. - - // Fill the buffers. - int attachmentFirstVertex = 0; - for (int smbi = 0; smbi < submeshInstructionCount; smbi++) { - var submeshInstruction = submeshInstructionsItems[smbi]; - var currentSubmeshBuffer = this.submeshes.Items[smbi]; - { //submesh.Resize(submesh.rawTriangleCount); - int newTriangleCount = submeshInstruction.rawTriangleCount; - if (newTriangleCount > currentSubmeshBuffer.Items.Length) - Array.Resize(ref currentSubmeshBuffer.Items, newTriangleCount); - else if (newTriangleCount < currentSubmeshBuffer.Items.Length) { - // Zero the extra. - var sbi = currentSubmeshBuffer.Items; - for (int ei = newTriangleCount, nn = sbi.Length; ei < nn; ei++) - sbi[ei] = 0; - } - currentSubmeshBuffer.Count = newTriangleCount; - } - - var tris = currentSubmeshBuffer.Items; - int triangleIndex = 0; - var skeleton = submeshInstruction.skeleton; - var drawOrderItems = skeleton.drawOrder.Items; - for (int slotIndex = submeshInstruction.startSlot, endSlot = submeshInstruction.endSlot; slotIndex < endSlot; slotIndex++) { - var slot = drawOrderItems[slotIndex]; - if (!slot.bone.active) continue; - - var attachment = drawOrderItems[slotIndex].attachment; - if (attachment is RegionAttachment) { - tris[triangleIndex] = attachmentFirstVertex; - tris[triangleIndex + 1] = attachmentFirstVertex + 2; - tris[triangleIndex + 2] = attachmentFirstVertex + 1; - tris[triangleIndex + 3] = attachmentFirstVertex + 2; - tris[triangleIndex + 4] = attachmentFirstVertex + 3; - tris[triangleIndex + 5] = attachmentFirstVertex + 1; - triangleIndex += 6; - attachmentFirstVertex += 4; - continue; - } - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - int[] attachmentTriangles = meshAttachment.triangles; - for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, triangleIndex++) - tris[triangleIndex] = attachmentFirstVertex + attachmentTriangles[ii]; - attachmentFirstVertex += meshAttachment.worldVerticesLength >> 1; // length/2; - } - } - } - } - } - - public void ScaleVertexData (float scale) { - var vbi = vertexBuffer.Items; - for (int i = 0, n = vertexBuffer.Count; i < n; i++) { - vbi[i] *= scale; // vbi[i].x *= scale; vbi[i].y *= scale; - } - - meshBoundsMin *= scale; - meshBoundsMax *= scale; - meshBoundsThickness *= scale; - } - - void AddAttachmentTintBlack (float r2, float g2, float b2, int vertexCount) { - var rg = new Vector2(r2, g2); - var bo = new Vector2(b2, 1f); - - int ovc = vertexBuffer.Count; - int newVertexCount = ovc + vertexCount; - { - if (uv2 == null) { - uv2 = new ExposedList(); - uv3 = new ExposedList(); - } - if (newVertexCount > uv2.Items.Length) { // Manual ExposedList.Resize() - Array.Resize(ref uv2.Items, newVertexCount); - Array.Resize(ref uv3.Items, newVertexCount); - } - uv2.Count = uv3.Count = newVertexCount; - } - - var uv2i = uv2.Items; - var uv3i = uv3.Items; - for (int i = 0; i < vertexCount; i++) { - uv2i[ovc + i] = rg; - uv3i[ovc + i] = bo; - } - } - #endregion - - #region Step 3 : Transfer vertex and triangle data to UnityEngine.Mesh - public void FillVertexData (Mesh mesh) { - var vbi = vertexBuffer.Items; - var ubi = uvBuffer.Items; - var cbi = colorBuffer.Items; - int vbiLength = vbi.Length; - - // Zero the extra. - { - int listCount = vertexBuffer.Count; - var vector3zero = Vector3.zero; - for (int i = listCount; i < vbiLength; i++) - vbi[i] = vector3zero; - } - - // Set the vertex buffer. - { - mesh.vertices = vbi; - mesh.uv = ubi; - mesh.colors32 = cbi; - - if (float.IsInfinity(meshBoundsMin.x)) { // meshBoundsMin.x == BoundsMinDefault // == doesn't work on float Infinity constants. - mesh.bounds = new Bounds(); - } else { - //mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax); - float halfWidth = (meshBoundsMax.x - meshBoundsMin.x) * 0.5f; - float halfHeight = (meshBoundsMax.y - meshBoundsMin.y) * 0.5f; - mesh.bounds = new Bounds { - center = new Vector3(meshBoundsMin.x + halfWidth, meshBoundsMin.y + halfHeight), - extents = new Vector3(halfWidth, halfHeight, meshBoundsThickness * 0.5f) - }; - } - } - - { - if (settings.addNormals) { - int oldLength = 0; - - if (normals == null) - normals = new Vector3[vbiLength]; - else - oldLength = normals.Length; - - if (oldLength != vbiLength) { - Array.Resize(ref this.normals, vbiLength); - var localNormals = this.normals; - for (int i = oldLength; i < vbiLength; i++) localNormals[i] = Vector3.back; - } - mesh.normals = this.normals; - } - - if (settings.tintBlack) { - if (uv2 != null) { - // Sometimes, the vertex buffer becomes smaller. We need to trim the size of the tint black buffers to match. - if (vbiLength != uv2.Items.Length) { - Array.Resize(ref uv2.Items, vbiLength); - Array.Resize(ref uv3.Items, vbiLength); - uv2.Count = uv3.Count = vbiLength; - } - mesh.uv2 = this.uv2.Items; - mesh.uv3 = this.uv3.Items; - } - } - } - } - - public void FillLateVertexData (Mesh mesh) { - if (settings.calculateTangents) { - int vertexCount = this.vertexBuffer.Count; - var sbi = submeshes.Items; - int submeshCount = submeshes.Count; - var vbi = vertexBuffer.Items; - var ubi = uvBuffer.Items; - - MeshGenerator.SolveTangents2DEnsureSize(ref this.tangents, ref this.tempTanBuffer, vertexCount, vbi.Length); - for (int i = 0; i < submeshCount; i++) { - var submesh = sbi[i].Items; - int triangleCount = sbi[i].Count; - MeshGenerator.SolveTangents2DTriangles(this.tempTanBuffer, submesh, triangleCount, vbi, ubi, vertexCount); - } - MeshGenerator.SolveTangents2DBuffer(this.tangents, this.tempTanBuffer, vertexCount); - mesh.tangents = this.tangents; - } - } - - public void FillTriangles (Mesh mesh) { - int submeshCount = submeshes.Count; - var submeshesItems = submeshes.Items; - mesh.subMeshCount = submeshCount; - - for (int i = 0; i < submeshCount; i++) - mesh.SetTriangles(submeshesItems[i].Items, i, false); - } - - public void FillTrianglesSingle (Mesh mesh) { - mesh.SetTriangles(submeshes.Items[0].Items, 0, false); - } - #endregion - - public void EnsureVertexCapacity (int minimumVertexCount, bool inlcudeTintBlack = false, bool includeTangents = false, bool includeNormals = false) { - if (minimumVertexCount > vertexBuffer.Items.Length) { - Array.Resize(ref vertexBuffer.Items, minimumVertexCount); - Array.Resize(ref uvBuffer.Items, minimumVertexCount); - Array.Resize(ref colorBuffer.Items, minimumVertexCount); - - if (inlcudeTintBlack) { - if (uv2 == null) { - uv2 = new ExposedList(minimumVertexCount); - uv3 = new ExposedList(minimumVertexCount); - } - uv2.Resize(minimumVertexCount); - uv3.Resize(minimumVertexCount); - } - - if (includeNormals) { - if (normals == null) - normals = new Vector3[minimumVertexCount]; - else - Array.Resize(ref normals, minimumVertexCount); - - } - - if (includeTangents) { - if (tangents == null) - tangents = new Vector4[minimumVertexCount]; - else - Array.Resize(ref tangents, minimumVertexCount); - } - } - } - - /// Trims internal buffers to reduce the resulting mesh data stream size. - public void TrimExcess () { - vertexBuffer.TrimExcess(); - uvBuffer.TrimExcess(); - colorBuffer.TrimExcess(); - - if (uv2 != null) uv2.TrimExcess(); - if (uv3 != null) uv3.TrimExcess(); - - int vbiLength = vertexBuffer.Items.Length; - if (normals != null) Array.Resize(ref normals, vbiLength); - if (tangents != null) Array.Resize(ref tangents, vbiLength); - } - - #region TangentSolver2D - // Thanks to contributions from forum user ToddRivers - - /// Step 1 of solving tangents. Ensure you have buffers of the correct size. - /// Eventual Vector4[] tangent buffer to assign to Mesh.tangents. - /// Temporary Vector2 buffer for calculating directions. - /// Number of vertices that require tangents (or the size of the vertex array) - internal static void SolveTangents2DEnsureSize (ref Vector4[] tangentBuffer, ref Vector2[] tempTanBuffer, int vertexCount, int vertexBufferLength) { - if (tangentBuffer == null || tangentBuffer.Length != vertexBufferLength) - tangentBuffer = new Vector4[vertexBufferLength]; - - if (tempTanBuffer == null || tempTanBuffer.Length < vertexCount * 2) - tempTanBuffer = new Vector2[vertexCount * 2]; // two arrays in one. - } - - /// Step 2 of solving tangents. Fills (part of) a temporary tangent-solution buffer based on the vertices and uvs defined by a submesh's triangle buffer. Only needs to be called once for single-submesh meshes. - /// A temporary Vector3[] for calculating tangents. - /// The mesh's current vertex position buffer. - /// The mesh's current triangles buffer. - /// The mesh's current uvs buffer. - /// Number of vertices that require tangents (or the size of the vertex array) - /// The number of triangle indexes in the triangle array to be used. - internal static void SolveTangents2DTriangles (Vector2[] tempTanBuffer, int[] triangles, int triangleCount, Vector3[] vertices, Vector2[] uvs, int vertexCount) { - Vector2 sdir; - Vector2 tdir; - for (int t = 0; t < triangleCount; t += 3) { - int i1 = triangles[t + 0]; - int i2 = triangles[t + 1]; - int i3 = triangles[t + 2]; - - Vector3 v1 = vertices[i1]; - Vector3 v2 = vertices[i2]; - Vector3 v3 = vertices[i3]; - - Vector2 w1 = uvs[i1]; - Vector2 w2 = uvs[i2]; - Vector2 w3 = uvs[i3]; - - float x1 = v2.x - v1.x; - float x2 = v3.x - v1.x; - float y1 = v2.y - v1.y; - float y2 = v3.y - v1.y; - - float s1 = w2.x - w1.x; - float s2 = w3.x - w1.x; - float t1 = w2.y - w1.y; - float t2 = w3.y - w1.y; - - float div = s1 * t2 - s2 * t1; - float r = (div == 0f) ? 0f : 1f / div; - - sdir.x = (t2 * x1 - t1 * x2) * r; - sdir.y = (t2 * y1 - t1 * y2) * r; - tempTanBuffer[i1] = tempTanBuffer[i2] = tempTanBuffer[i3] = sdir; - - tdir.x = (s1 * x2 - s2 * x1) * r; - tdir.y = (s1 * y2 - s2 * y1) * r; - tempTanBuffer[vertexCount + i1] = tempTanBuffer[vertexCount + i2] = tempTanBuffer[vertexCount + i3] = tdir; - } - } - - /// Step 3 of solving tangents. Fills a Vector4[] tangents array according to values calculated in step 2. - /// A Vector4[] that will eventually be used to set Mesh.tangents - /// A temporary Vector3[] for calculating tangents. - /// Number of vertices that require tangents (or the size of the vertex array) - internal static void SolveTangents2DBuffer (Vector4[] tangents, Vector2[] tempTanBuffer, int vertexCount) { - Vector4 tangent; - tangent.z = 0; - for (int i = 0; i < vertexCount; ++i) { - Vector2 t = tempTanBuffer[i]; - - // t.Normalize() (aggressively inlined). Even better if offloaded to GPU via vertex shader. - float magnitude = Mathf.Sqrt(t.x * t.x + t.y * t.y); - if (magnitude > 1E-05) { - float reciprocalMagnitude = 1f/magnitude; - t.x *= reciprocalMagnitude; - t.y *= reciprocalMagnitude; - } - - Vector2 t2 = tempTanBuffer[vertexCount + i]; - tangent.x = t.x; - tangent.y = t.y; - //tangent.z = 0; - tangent.w = (t.y * t2.x > t.x * t2.y) ? 1 : -1; // 2D direction calculation. Used for binormals. - tangents[i] = tangent; - } - } - #endregion - - #region AttachmentRendering - static List AttachmentVerts = new List(); - static List AttachmentUVs = new List(); - static List AttachmentColors32 = new List(); - static List AttachmentIndices = new List(); - - /// Fills mesh vertex data to render a RegionAttachment. - public static void FillMeshLocal (Mesh mesh, RegionAttachment regionAttachment) { - if (mesh == null) return; - if (regionAttachment == null) return; - - AttachmentVerts.Clear(); - var offsets = regionAttachment.Offset; - AttachmentVerts.Add(new Vector3(offsets[RegionAttachment.BLX], offsets[RegionAttachment.BLY])); - AttachmentVerts.Add(new Vector3(offsets[RegionAttachment.ULX], offsets[RegionAttachment.ULY])); - AttachmentVerts.Add(new Vector3(offsets[RegionAttachment.URX], offsets[RegionAttachment.URY])); - AttachmentVerts.Add(new Vector3(offsets[RegionAttachment.BRX], offsets[RegionAttachment.BRY])); - - AttachmentUVs.Clear(); - var uvs = regionAttachment.UVs; - AttachmentUVs.Add(new Vector2(uvs[RegionAttachment.ULX], uvs[RegionAttachment.ULY])); - AttachmentUVs.Add(new Vector2(uvs[RegionAttachment.URX], uvs[RegionAttachment.URY])); - AttachmentUVs.Add(new Vector2(uvs[RegionAttachment.BRX], uvs[RegionAttachment.BRY])); - AttachmentUVs.Add(new Vector2(uvs[RegionAttachment.BLX], uvs[RegionAttachment.BLY])); - - AttachmentColors32.Clear(); - Color32 c = (Color32)(new Color(regionAttachment.r, regionAttachment.g, regionAttachment.b, regionAttachment.a)); - for (int i = 0; i < 4; i++) - AttachmentColors32.Add(c); - - AttachmentIndices.Clear(); - AttachmentIndices.AddRange(new[] { 0, 2, 1, 0, 3, 2 }); - - mesh.Clear(); - mesh.name = regionAttachment.Name; - mesh.SetVertices(AttachmentVerts); - mesh.SetUVs(0, AttachmentUVs); - mesh.SetColors(AttachmentColors32); - mesh.SetTriangles(AttachmentIndices, 0); - mesh.RecalculateBounds(); - - AttachmentVerts.Clear(); - AttachmentUVs.Clear(); - AttachmentColors32.Clear(); - AttachmentIndices.Clear(); - } - - public static void FillMeshLocal (Mesh mesh, MeshAttachment meshAttachment, SkeletonData skeletonData) { - if (mesh == null) return; - if (meshAttachment == null) return; - int vertexCount = meshAttachment.WorldVerticesLength / 2; - - AttachmentVerts.Clear(); - if (meshAttachment.IsWeighted()) { - int count = meshAttachment.WorldVerticesLength; - int[] meshAttachmentBones = meshAttachment.bones; - int v = 0; - - float[] vertices = meshAttachment.vertices; - for (int w = 0, b = 0; w < count; w += 2) { - float wx = 0, wy = 0; - int n = meshAttachmentBones[v++]; - n += v; - for (; v < n; v++, b += 3) { - BoneMatrix bm = BoneMatrix.CalculateSetupWorld(skeletonData.bones.Items[meshAttachmentBones[v]]); - float vx = vertices[b], vy = vertices[b + 1], weight = vertices[b + 2]; - wx += (vx * bm.a + vy * bm.b + bm.x) * weight; - wy += (vx * bm.c + vy * bm.d + bm.y) * weight; - } - AttachmentVerts.Add(new Vector3(wx, wy)); - } - } else { - var localVerts = meshAttachment.Vertices; - Vector3 pos = default(Vector3); - for (int i = 0; i < vertexCount; i++) { - int ii = i * 2; - pos.x = localVerts[ii]; - pos.y = localVerts[ii + 1]; - AttachmentVerts.Add(pos); - } - } - - var uvs = meshAttachment.uvs; - Vector2 uv = default(Vector2); - Color32 c = (Color32)(new Color(meshAttachment.r, meshAttachment.g, meshAttachment.b, meshAttachment.a)); - AttachmentUVs.Clear(); - AttachmentColors32.Clear(); - for (int i = 0; i < vertexCount; i++) { - int ii = i * 2; - uv.x = uvs[ii]; - uv.y = uvs[ii + 1]; - AttachmentUVs.Add(uv); - - AttachmentColors32.Add(c); - } - - AttachmentIndices.Clear(); - AttachmentIndices.AddRange(meshAttachment.triangles); - - mesh.Clear(); - mesh.name = meshAttachment.Name; - mesh.SetVertices(AttachmentVerts); - mesh.SetUVs(0, AttachmentUVs); - mesh.SetColors(AttachmentColors32); - mesh.SetTriangles(AttachmentIndices, 0); - mesh.RecalculateBounds(); - - AttachmentVerts.Clear(); - AttachmentUVs.Clear(); - AttachmentColors32.Clear(); - AttachmentIndices.Clear(); - } - #endregion - } - - /// A double-buffered Mesh, and a shared material array, bundled for use by Spine components that need to push a Mesh and materials to a Unity MeshRenderer and MeshFilter. - public class MeshRendererBuffers : IDisposable { - DoubleBuffered doubleBufferedMesh; - internal readonly ExposedList submeshMaterials = new ExposedList(); - internal Material[] sharedMaterials = new Material[0]; - - public void Initialize () { - if (doubleBufferedMesh != null) { - doubleBufferedMesh.GetNext().Clear(); - doubleBufferedMesh.GetNext().Clear(); - submeshMaterials.Clear(); - } else { - doubleBufferedMesh = new DoubleBuffered(); - } - } - - /// Returns a sharedMaterials array for use on a MeshRenderer. - /// - public Material[] GetUpdatedSharedMaterialsArray () { - if (submeshMaterials.Count == sharedMaterials.Length) - submeshMaterials.CopyTo(sharedMaterials); - else - sharedMaterials = submeshMaterials.ToArray(); - - return sharedMaterials; - } - - /// Returns true if the materials were modified since the buffers were last updated. - public bool MaterialsChangedInLastUpdate () { - int newSubmeshMaterials = submeshMaterials.Count; - var sharedMaterials = this.sharedMaterials; - if (newSubmeshMaterials != sharedMaterials.Length) return true; - - var submeshMaterialsItems = submeshMaterials.Items; - for (int i = 0; i < newSubmeshMaterials; i++) - if (!Material.ReferenceEquals(submeshMaterialsItems[i], sharedMaterials[i])) return true; //if (submeshMaterialsItems[i].GetInstanceID() != sharedMaterials[i].GetInstanceID()) return true; - - return false; - } - - /// Updates the internal shared materials array with the given instruction list. - public void UpdateSharedMaterials (ExposedList instructions) { - int newSize = instructions.Count; - { //submeshMaterials.Resize(instructions.Count); - if (newSize > submeshMaterials.Items.Length) - Array.Resize(ref submeshMaterials.Items, newSize); - submeshMaterials.Count = newSize; - } - - var submeshMaterialsItems = submeshMaterials.Items; - var instructionsItems = instructions.Items; - for (int i = 0; i < newSize; i++) - submeshMaterialsItems[i] = instructionsItems[i].material; - } - - public SmartMesh GetNextMesh () { - return doubleBufferedMesh.GetNext(); - } - - public void Clear () { - sharedMaterials = new Material[0]; - submeshMaterials.Clear(); - } - - public void Dispose () { - if (doubleBufferedMesh == null) return; - doubleBufferedMesh.GetNext().Dispose(); - doubleBufferedMesh.GetNext().Dispose(); - doubleBufferedMesh = null; - } - - ///This is a Mesh that also stores the instructions SkeletonRenderer generated for it. - public class SmartMesh : IDisposable { - public Mesh mesh = SpineMesh.NewSkeletonMesh(); - public SkeletonRendererInstruction instructionUsed = new SkeletonRendererInstruction(); - - public void Clear () { - mesh.Clear(); - instructionUsed.Clear(); - } - - public void Dispose () { - if (mesh != null) { - #if UNITY_EDITOR - if (Application.isEditor && !Application.isPlaying) - UnityEngine.Object.DestroyImmediate(mesh); - else - UnityEngine.Object.Destroy(mesh); - #else - UnityEngine.Object.Destroy(mesh); - #endif - } - mesh = null; - } - } - } - - /// Instructions used by a SkeletonRenderer to render a mesh. - public class SkeletonRendererInstruction { - public readonly ExposedList submeshInstructions = new ExposedList(); - - public bool immutableTriangles; - #if SPINE_TRIANGLECHECK - public bool hasActiveClipping; - public int rawVertexCount = -1; - public readonly ExposedList attachments = new ExposedList(); - #endif - - public void Clear () { - #if SPINE_TRIANGLECHECK - this.attachments.Clear(false); - rawVertexCount = -1; - hasActiveClipping = false; - #endif - this.submeshInstructions.Clear(false); - } - - public void Dispose () { - attachments.Clear(true); - } - - public void SetWithSubset (ExposedList instructions, int startSubmesh, int endSubmesh) { - #if SPINE_TRIANGLECHECK - int runningVertexCount = 0; - #endif - - var submeshes = this.submeshInstructions; - submeshes.Clear(false); - int submeshCount = endSubmesh - startSubmesh; - submeshes.Resize(submeshCount); - var submeshesItems = submeshes.Items; - var instructionsItems = instructions.Items; - for (int i = 0; i < submeshCount; i++) { - var instruction = instructionsItems[startSubmesh + i]; - submeshesItems[i] = instruction; - #if SPINE_TRIANGLECHECK - this.hasActiveClipping |= instruction.hasClipping; - submeshesItems[i].rawFirstVertexIndex = runningVertexCount; // Ensure current instructions have correct cached values. - runningVertexCount += instruction.rawVertexCount; // vertexCount will also be used for the rest of this method. - #endif - } - #if SPINE_TRIANGLECHECK - this.rawVertexCount = runningVertexCount; - - // assumption: instructions are contiguous. start and end are valid within instructions. - - int startSlot = instructionsItems[startSubmesh].startSlot; - int endSlot = instructionsItems[endSubmesh - 1].endSlot; - attachments.Clear(false); - int attachmentCount = endSlot - startSlot; - attachments.Resize(attachmentCount); - var attachmentsItems = attachments.Items; - - var drawOrderItems = instructionsItems[0].skeleton.drawOrder.Items; - for (int i = 0; i < attachmentCount; i++) { - Slot slot = drawOrderItems[startSlot + i]; - if (!slot.bone.active) continue; - attachmentsItems[i] = slot.attachment; - } - - #endif - } - - public void Set (SkeletonRendererInstruction other) { - this.immutableTriangles = other.immutableTriangles; - - #if SPINE_TRIANGLECHECK - this.hasActiveClipping = other.hasActiveClipping; - this.rawVertexCount = other.rawVertexCount; - this.attachments.Clear(false); - this.attachments.EnsureCapacity(other.attachments.Capacity); - this.attachments.Count = other.attachments.Count; - other.attachments.CopyTo(this.attachments.Items); - #endif - - this.submeshInstructions.Clear(false); - this.submeshInstructions.EnsureCapacity(other.submeshInstructions.Capacity); - this.submeshInstructions.Count = other.submeshInstructions.Count; - other.submeshInstructions.CopyTo(this.submeshInstructions.Items); - } - - public static bool GeometryNotEqual (SkeletonRendererInstruction a, SkeletonRendererInstruction b) { - #if SPINE_TRIANGLECHECK - #if UNITY_EDITOR - if (!Application.isPlaying) - return true; - #endif - - if (a.hasActiveClipping || b.hasActiveClipping) return true; // Triangles are unpredictable when clipping is active. - - // Everything below assumes the raw vertex and triangle counts were used. (ie, no clipping was done) - if (a.rawVertexCount != b.rawVertexCount) return true; - - if (a.immutableTriangles != b.immutableTriangles) return true; - - int attachmentCountB = b.attachments.Count; - if (a.attachments.Count != attachmentCountB) return true; // Bounds check for the looped storedAttachments count below. - - // Submesh count changed - int submeshCountA = a.submeshInstructions.Count; - int submeshCountB = b.submeshInstructions.Count; - if (submeshCountA != submeshCountB) return true; - - // Submesh Instruction mismatch - var submeshInstructionsItemsA = a.submeshInstructions.Items; - var submeshInstructionsItemsB = b.submeshInstructions.Items; - - var attachmentsA = a.attachments.Items; - var attachmentsB = b.attachments.Items; - for (int i = 0; i < attachmentCountB; i++) - if (!System.Object.ReferenceEquals(attachmentsA[i], attachmentsB[i])) return true; - - for (int i = 0; i < submeshCountB; i++) { - var submeshA = submeshInstructionsItemsA[i]; - var submeshB = submeshInstructionsItemsB[i]; - - if (!( - submeshA.rawVertexCount == submeshB.rawVertexCount && - submeshA.startSlot == submeshB.startSlot && - submeshA.endSlot == submeshB.endSlot - && submeshA.rawTriangleCount == submeshB.rawTriangleCount && - submeshA.rawFirstVertexIndex == submeshB.rawFirstVertexIndex - )) - return true; - } - - return false; - #else - // In normal immutable triangle use, immutableTriangles will be initially false, forcing the smartmesh to update the first time but never again after that, unless there was an immutableTriangles flag mismatch.. - if (a.immutableTriangles || b.immutableTriangles) - return (a.immutableTriangles != b.immutableTriangles); - - return true; - #endif - } - } - - + } }