diff --git a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs index f81795313..4715d4732 100644 --- a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs +++ b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs @@ -919,6 +919,10 @@ namespace Spine.Unity.Editor { public PointAttachment NewPointAttachment (Skin skin, string name) { return new PointAttachment(name); } + + public ClippingAttachment NewClippingAttachment (Skin skin, string name) { + return new ClippingAttachment(name); + } } #endregion diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysMeshGenerator.cs b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysMeshGenerator.cs index d40973c7a..e69de29bb 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysMeshGenerator.cs @@ -1,488 +0,0 @@ -/****************************************************************************** - * Spine Runtimes Software License v2.5 - * - * Copyright (c) 2013-2016, Esoteric Software - * All rights reserved. - * - * You are granted a perpetual, non-exclusive, non-sublicensable, and - * non-transferable license to use, install, execute, and perform the Spine - * Runtimes software and derivative works solely for personal or internal - * use. Without the written permission of Esoteric Software (see Section 2 of - * the Spine Software License Agreement), you may not (a) modify, translate, - * adapt, or develop new applications using the Spine Runtimes or otherwise - * create derivative works or improvements of the Spine Runtimes or (b) remove, - * delete, alter, or obscure any trademarks or any copyright, trademark, patent, - * or other intellectual property or proprietary rights notices on or in the - * Software, including any copy thereof. Redistributions in binary or source - * form must include this license and terms. - * - * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF - * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -#define SPINE_OPTIONAL_NORMALS -using UnityEngine; - -namespace Spine.Unity.MeshGeneration { - public class ArraysMeshGenerator { - #region Settings - public bool PremultiplyVertexColors { get; set; } - protected bool addNormals; - public bool AddNormals { get { return addNormals; } set { addNormals = value; } } - protected bool addTangents; - public bool AddTangents { get { return addTangents; } set { addTangents = value; } } - protected bool addBlackTint; - public bool AddBlackTint { get { return addBlackTint; } set { addBlackTint = value; } } - #endregion - - protected float[] attachmentVertexBuffer = new float[8]; - protected Vector3[] meshVertices; - protected Color32[] meshColors32; - protected Vector2[] meshUVs; - - #if SPINE_OPTIONAL_NORMALS - protected Vector3[] meshNormals; - #endif - protected Vector4[] meshTangents; - protected Vector2[] tempTanBuffer; - - protected Vector2[] uv2, uv3; // Black tint - - public void TryAddNormalsTo (Mesh mesh, int targetVertexCount) { - #if SPINE_OPTIONAL_NORMALS - if (addNormals) { - bool verticesWasResized = this.meshNormals == null || meshNormals.Length < targetVertexCount; - if (verticesWasResized) { - this.meshNormals = new Vector3[targetVertexCount]; - Vector3 fixedNormal = new Vector3(0, 0, -1f); - Vector3[] normals = this.meshNormals; - for (int i = 0; i < targetVertexCount; i++) - normals[i] = fixedNormal; - } - - mesh.normals = this.meshNormals; - } - #endif - } - - /// Ensures the sizes of the passed array references. If they are not the correct size, a new array will be assigned to the references. - /// true, if a resize occurred, false otherwise. - public static bool EnsureSize (int targetVertexCount, ref Vector3[] vertices, ref Vector2[] uvs, ref Color32[] colors) { - Vector3[] verts = vertices; - bool verticesWasResized = verts == null || targetVertexCount > verts.Length; - if (verticesWasResized) { - // Not enough space, increase size. - vertices = new Vector3[targetVertexCount]; - colors = new Color32[targetVertexCount]; - uvs = new Vector2[targetVertexCount]; - } else { - // Too many vertices, zero the extra. - Vector3 zero = Vector3.zero; - for (int i = targetVertexCount, n = verts.Length; i < n; i++) - verts[i] = zero; - } - return verticesWasResized; - } - - public static bool EnsureSize (int targetVertexCount, ref Vector2[] buffer) { - Vector2[] buff = buffer; - bool verticesWasResized = (buffer == null || targetVertexCount > buffer.Length); - if (verticesWasResized) { - buffer = new Vector2[targetVertexCount]; - } else { - Vector3 zero = Vector3.zero; - for (int i = targetVertexCount, n = buff.Length; i < n; i++) - buff[i] = zero; - } - return verticesWasResized; - } - - public static bool EnsureTriangleBuffersSize (ExposedList submeshBuffers, int targetSubmeshCount, SubmeshInstruction[] instructionItems) { - bool submeshBuffersWasResized = submeshBuffers.Count < targetSubmeshCount; - if (submeshBuffersWasResized) { - submeshBuffers.GrowIfNeeded(targetSubmeshCount - submeshBuffers.Count); - for (int i = submeshBuffers.Count; submeshBuffers.Count < targetSubmeshCount; i++) - submeshBuffers.Add(new SubmeshTriangleBuffer(instructionItems[i].triangleCount)); - } - return submeshBuffersWasResized; - } - - public static void FillBlackUVs (Skeleton skeleton, int startSlot, int endSlot, Vector2[] uv2, Vector2[] uv3, int vertexIndex, bool renderMeshes = true) { - var skeletonDrawOrderItems = skeleton.DrawOrder.Items; - Vector2 rg, b2; - int vi = vertexIndex; - b2.y = 1f; - - // drawOrder[endSlot] is excluded - for (int slotIndex = startSlot; slotIndex < endSlot; slotIndex++) { - var slot = skeletonDrawOrderItems[slotIndex]; - var attachment = slot.attachment; - - rg.x = slot.r2; //r - rg.y = slot.g2; //g - b2.x = slot.b2; //b - - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - uv2[vi] = rg; uv2[vi + 1] = rg; uv2[vi + 2] = rg; uv2[vi + 3] = rg; - uv3[vi] = b2; uv3[vi + 1] = b2; uv3[vi + 2] = b2; uv3[vi + 3] = b2; - vi += 4; - } else if (renderMeshes) { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - int meshVertexCount = meshAttachment.worldVerticesLength; - for (int iii = 0; iii < meshVertexCount; iii += 2) { - uv2[vi] = rg; - uv3[vi] = b2; - vi++; - } - } - } - } - } - - /// Fills Unity vertex data buffers with verts from the Spine Skeleton. - /// Spine.Skeleton source of the drawOrder array - /// Slot index of the first slot. - /// The index bounding the slot list. [endSlot - 1] is the last slot to be added. - /// Spacing along the z-axis between attachments. - /// If set to true, vertex colors will be premultiplied. This will also enable additive. - /// Vertex positions array. - /// Vertex UV array. - /// Vertex color array (Color32). - /// A reference to the running vertex index. This is used when more than one submesh is to be added. - /// A temporary vertex position buffer for attachment position values. - /// Reference to the running calculated minimum bounds. - /// Reference to the running calculated maximum bounds. - /// Include MeshAttachments. If false, it will ignore MeshAttachments. - public static void FillVerts (Skeleton skeleton, int startSlot, int endSlot, float zSpacing, bool pmaColors, Vector3[] verts, Vector2[] uvs, Color32[] colors, ref int vertexIndex, ref float[] tempVertBuffer, ref Vector3 boundsMin, ref Vector3 boundsMax, bool renderMeshes = true) { - Color32 color; - var skeletonDrawOrderItems = skeleton.DrawOrder.Items; - float a = skeleton.a * 255, r = skeleton.r, g = skeleton.g, b = skeleton.b; - - int vi = vertexIndex; - var tempVerts = tempVertBuffer; - Vector3 bmin = boundsMin; - Vector3 bmax = boundsMax; - - // drawOrder[endSlot] is excluded - for (int slotIndex = startSlot; slotIndex < endSlot; slotIndex++) { - var slot = skeletonDrawOrderItems[slotIndex]; - var attachment = slot.attachment; - float z = slotIndex * zSpacing; - - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - regionAttachment.ComputeWorldVertices(slot.bone, tempVerts, 0); - - float x1 = tempVerts[RegionAttachment.BLX], y1 = tempVerts[RegionAttachment.BLY]; - float x2 = tempVerts[RegionAttachment.ULX], y2 = tempVerts[RegionAttachment.ULY]; - float x3 = tempVerts[RegionAttachment.URX], y3 = tempVerts[RegionAttachment.URY]; - float x4 = tempVerts[RegionAttachment.BRX], y4 = tempVerts[RegionAttachment.BRY]; - verts[vi].x = x1; verts[vi].y = y1; verts[vi].z = z; - verts[vi + 1].x = x4; verts[vi + 1].y = y4; verts[vi + 1].z = z; - verts[vi + 2].x = x2; verts[vi + 2].y = y2; verts[vi + 2].z = z; - verts[vi + 3].x = x3; verts[vi + 3].y = y3; verts[vi + 3].z = z; - - if (pmaColors) { - color.a = (byte)(a * slot.a * regionAttachment.a); - color.r = (byte)(r * slot.r * regionAttachment.r * color.a); - color.g = (byte)(g * slot.g * regionAttachment.g * color.a); - color.b = (byte)(b * slot.b * regionAttachment.b * color.a); - if (slot.data.blendMode == BlendMode.Additive) color.a = 0; - } else { - color.a = (byte)(a * slot.a * regionAttachment.a); - color.r = (byte)(r * slot.r * regionAttachment.r * 255); - color.g = (byte)(g * slot.g * regionAttachment.g * 255); - color.b = (byte)(b * slot.b * regionAttachment.b * 255); - } - - colors[vi] = color; colors[vi + 1] = color; colors[vi + 2] = color; colors[vi + 3] = color; - - float[] regionUVs = regionAttachment.uvs; - uvs[vi].x = regionUVs[RegionAttachment.BLX]; uvs[vi].y = regionUVs[RegionAttachment.BLY]; - uvs[vi + 1].x = regionUVs[RegionAttachment.BRX]; uvs[vi + 1].y = regionUVs[RegionAttachment.BRY]; - uvs[vi + 2].x = regionUVs[RegionAttachment.ULX]; uvs[vi + 2].y = regionUVs[RegionAttachment.ULY]; - uvs[vi + 3].x = regionUVs[RegionAttachment.URX]; uvs[vi + 3].y = regionUVs[RegionAttachment.URY]; - - if (x1 < bmin.x) bmin.x = x1; // Potential first attachment bounds initialization. Initial min should not block initial max. Same for Y below. - if (x1 > bmax.x) bmax.x = x1; - if (x2 < bmin.x) bmin.x = x2; - else if (x2 > bmax.x) bmax.x = x2; - if (x3 < bmin.x) bmin.x = x3; - else if (x3 > bmax.x) bmax.x = x3; - if (x4 < bmin.x) bmin.x = x4; - else if (x4 > bmax.x) bmax.x = x4; - - if (y1 < bmin.y) bmin.y = y1; - if (y1 > bmax.y) bmax.y = y1; - if (y2 < bmin.y) bmin.y = y2; - else if (y2 > bmax.y) bmax.y = y2; - if (y3 < bmin.y) bmin.y = y3; - else if (y3 > bmax.y) bmax.y = y3; - if (y4 < bmin.y) bmin.y = y4; - else if (y4 > bmax.y) bmax.y = y4; - - vi += 4; - } else if (renderMeshes) { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - int meshVertexCount = meshAttachment.worldVerticesLength; - if (tempVerts.Length < meshVertexCount) tempVerts = new float[meshVertexCount]; - meshAttachment.ComputeWorldVertices(slot, tempVerts); - - if (pmaColors) { - color.a = (byte)(a * slot.a * meshAttachment.a); - color.r = (byte)(r * slot.r * meshAttachment.r * color.a); - color.g = (byte)(g * slot.g * meshAttachment.g * color.a); - color.b = (byte)(b * slot.b * meshAttachment.b * color.a); - if (slot.data.blendMode == BlendMode.Additive) color.a = 0; - } else { - color.a = (byte)(a * slot.a * meshAttachment.a); - color.r = (byte)(r * slot.r * meshAttachment.r * 255); - color.g = (byte)(g * slot.g * meshAttachment.g * 255); - color.b = (byte)(b * slot.b * meshAttachment.b * 255); - } - - float[] attachmentUVs = meshAttachment.uvs; - - // Potential first attachment bounds initialization. See conditions in RegionAttachment logic. - if (vi == vertexIndex) { - // Initial min should not block initial max. - // vi == vertexIndex does not always mean the bounds are fresh. It could be a submesh. Do not nuke old values by omitting the check. - // Should know that this is the first attachment in the submesh. slotIndex == startSlot could be an empty slot. - float fx = tempVerts[0], fy = tempVerts[1]; - if (fx < bmin.x) bmin.x = fx; - if (fx > bmax.x) bmax.x = fx; - if (fy < bmin.y) bmin.y = fy; - if (fy > bmax.y) bmax.y = fy; - } - - for (int iii = 0; iii < meshVertexCount; iii += 2) { - float x = tempVerts[iii], y = tempVerts[iii + 1]; - verts[vi].x = x; verts[vi].y = y; verts[vi].z = z; - colors[vi] = color; uvs[vi].x = attachmentUVs[iii]; uvs[vi].y = attachmentUVs[iii + 1]; - - if (x < bmin.x) bmin.x = x; - else if (x > bmax.x) bmax.x = x; - - if (y < bmin.y) bmin.y = y; - else if (y > bmax.y) bmax.y = y; - - vi++; - } - } - } - } - - // ref return values - vertexIndex = vi; - tempVertBuffer = tempVerts; - boundsMin = bmin; - boundsMax = bmax; - } - - - /// Fills a submesh triangle buffer array. - /// Spine.Skeleton source of draw order slots. - /// The target triangle count. - /// First vertex of this submesh. - /// Start slot. - /// End slot. - /// The triangle buffer array to be filled. This reference will be replaced in case the triangle values don't fit. - /// If set to true, the triangle buffer is allowed to be larger than needed. - public static void FillTriangles (ref int[] triangleBuffer, Skeleton skeleton, int triangleCount, int firstVertex, int startSlot, int endSlot, bool isLastSubmesh) { - int trianglesCapacity = triangleBuffer.Length; - int[] tris = triangleBuffer; - - if (isLastSubmesh) { - if (trianglesCapacity > triangleCount) { - for (int i = triangleCount; i < trianglesCapacity; i++) - tris[i] = 0; - } else if (trianglesCapacity < triangleCount) { - triangleBuffer = tris = new int[triangleCount]; - } - } else if (trianglesCapacity != triangleCount) { - triangleBuffer = tris = new int[triangleCount]; - } - - var skeletonDrawOrderItems = skeleton.drawOrder.Items; - for (int i = startSlot, n = endSlot, ti = 0, afv = firstVertex; i < n; i++) { - var attachment = skeletonDrawOrderItems[i].attachment; - - // RegionAttachment - if (attachment is RegionAttachment) { - tris[ti] = afv; - tris[ti + 1] = afv + 2; - tris[ti + 2] = afv + 1; - tris[ti + 3] = afv + 2; - tris[ti + 4] = afv + 3; - tris[ti + 5] = afv + 1; - ti += 6; - afv += 4; - continue; - } - - // MeshAttachment - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - int[] attachmentTriangles = meshAttachment.triangles; - for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, ti++) - tris[ti] = afv + attachmentTriangles[ii]; - - afv += meshAttachment.worldVerticesLength >> 1; // length/2; - } - - } - } - - public static void FillTrianglesQuads (ref int[] triangleBuffer, ref int storedTriangleCount, ref int storedFirstVertex, int instructionsFirstVertex, int instructionTriangleCount, bool isLastSubmesh) { - int trianglesCapacity = triangleBuffer.Length; - - if (isLastSubmesh && trianglesCapacity > instructionTriangleCount) { - for (int i = instructionTriangleCount; i < trianglesCapacity; i++) - triangleBuffer[i] = 0; - storedTriangleCount = instructionTriangleCount; - } else if (trianglesCapacity != instructionTriangleCount) { - triangleBuffer = new int[instructionTriangleCount]; - storedTriangleCount = 0; - } - - // Use stored quad triangles if possible. - int[] tris = triangleBuffer; - if (storedFirstVertex != instructionsFirstVertex || storedTriangleCount < instructionTriangleCount) { //|| storedTriangleCount == 0 - storedTriangleCount = instructionTriangleCount; - storedFirstVertex = instructionsFirstVertex; - int afv = instructionsFirstVertex; // attachment first vertex - for (int ti = 0; ti < instructionTriangleCount; ti += 6, afv += 4) { - tris[ti] = afv; - tris[ti + 1] = afv + 2; - tris[ti + 2] = afv + 1; - tris[ti + 3] = afv + 2; - tris[ti + 4] = afv + 3; - tris[ti + 5] = afv + 1; - } - } - } - - /// Creates a UnityEngine.Bounds struct from minimum and maximum value vectors. - public static Bounds ToBounds (Vector3 boundsMin, Vector3 boundsMax) { - Vector3 size = (boundsMax - boundsMin); - return new Bounds((boundsMin + (size * 0.5f)), size); - } - - #region TangentSolver2D - // Thanks to contributions from forum user ToddRivers - - /// 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) - public static void SolveTangents2DEnsureSize (ref Vector4[] tangentBuffer, ref Vector2[] tempTanBuffer, int vertexCount) { - if (tangentBuffer == null || tangentBuffer.Length < vertexCount) - tangentBuffer = new Vector4[vertexCount]; - - if (tempTanBuffer == null || tempTanBuffer.Length < vertexCount * 2) - tempTanBuffer = new Vector2[vertexCount * 2]; // two arrays in one. - } - - /// 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. - public static void SolveTangents2DTriangles (Vector2[] tempTanBuffer, int[] triangles, int triangleCount, Vector3[] vertices, Vector2[] uvs, int vertexCount) { - Vector2 sdir; - Vector2 tdir; - for (int t = 0; t < triangleCount; t += 3) { - int i1 = triangles[t + 0]; - int i2 = triangles[t + 1]; - int i3 = triangles[t + 2]; - - Vector3 v1 = vertices[i1]; - Vector3 v2 = vertices[i2]; - Vector3 v3 = vertices[i3]; - - Vector2 w1 = uvs[i1]; - Vector2 w2 = uvs[i2]; - Vector2 w3 = uvs[i3]; - - float x1 = v2.x - v1.x; - float x2 = v3.x - v1.x; - float y1 = v2.y - v1.y; - float y2 = v3.y - v1.y; - - float s1 = w2.x - w1.x; - float s2 = w3.x - w1.x; - float t1 = w2.y - w1.y; - float t2 = w3.y - w1.y; - - float div = s1 * t2 - s2 * t1; - float r = (div == 0f) ? 0f : 1f / div; - - sdir.x = (t2 * x1 - t1 * x2) * r; - sdir.y = (t2 * y1 - t1 * y2) * r; - tempTanBuffer[i1] = tempTanBuffer[i2] = tempTanBuffer[i3] = sdir; - - tdir.x = (s1 * x2 - s2 * x1) * r; - tdir.y = (s1 * y2 - s2 * y1) * r; - tempTanBuffer[vertexCount + i1] = tempTanBuffer[vertexCount + i2] = tempTanBuffer[vertexCount + i3] = tdir; - } - } - - /// 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) - public static void SolveTangents2DBuffer (Vector4[] tangents, Vector2[] tempTanBuffer, int vertexCount) { - - Vector4 tangent; - tangent.z = 0; - for (int i = 0; i < vertexCount; ++i) { - Vector2 t = tempTanBuffer[i]; - - // t.Normalize() (aggressively inlined). Even better if offloaded to GPU via vertex shader. - float magnitude = Mathf.Sqrt(t.x * t.x + t.y * t.y); - if (magnitude > 1E-05) { - float reciprocalMagnitude = 1f/magnitude; - t.x *= reciprocalMagnitude; - t.y *= reciprocalMagnitude; - } - - Vector2 t2 = tempTanBuffer[vertexCount + i]; - tangent.x = t.x; - tangent.y = t.y; - //tangent.z = 0; - tangent.w = (t.y * t2.x > t.x * t2.y) ? 1 : -1; // 2D direction calculation. Used for binormals. - tangents[i] = tangent; - } - - } - #endregion - - #region SubmeshTriangleBuffer - public class SubmeshTriangleBuffer { - public int[] triangles; - public int triangleCount; // for last/single submeshes with potentially zeroed triangles. - public int firstVertex = -1; // for !renderMeshes. - - public SubmeshTriangleBuffer () { } - - public SubmeshTriangleBuffer (int triangleCount) { - triangles = new int[triangleCount]; - } - } - #endregion - - } -} diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSimpleMeshGenerator.cs b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSimpleMeshGenerator.cs index e4d9de211..e69de29bb 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSimpleMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSimpleMeshGenerator.cs @@ -1,144 +0,0 @@ -/****************************************************************************** - * Spine Runtimes Software License v2.5 - * - * Copyright (c) 2013-2016, Esoteric Software - * All rights reserved. - * - * You are granted a perpetual, non-exclusive, non-sublicensable, and - * non-transferable license to use, install, execute, and perform the Spine - * Runtimes software and derivative works solely for personal or internal - * use. Without the written permission of Esoteric Software (see Section 2 of - * the Spine Software License Agreement), you may not (a) modify, translate, - * adapt, or develop new applications using the Spine Runtimes or otherwise - * create derivative works or improvements of the Spine Runtimes or (b) remove, - * delete, alter, or obscure any trademarks or any copyright, trademark, patent, - * or other intellectual property or proprietary rights notices on or in the - * Software, including any copy thereof. Redistributions in binary or source - * form must include this license and terms. - * - * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF - * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -using UnityEngine; - -namespace Spine.Unity.MeshGeneration { - public class ArraysSimpleMeshGenerator : ArraysMeshGenerator, ISimpleMeshGenerator { - #region Settings - protected float scale = 1f; - public float Scale { get { return scale; } set { scale = value; } } - public float ZSpacing { get; set; } - #endregion - - protected Mesh lastGeneratedMesh; - public Mesh LastGeneratedMesh { get { return lastGeneratedMesh; } } - - readonly DoubleBufferedMesh doubleBufferedMesh = new DoubleBufferedMesh(); - int[] triangles; - - public Mesh GenerateMesh (Skeleton skeleton) { - int totalVertexCount = 0; // size of vertex arrays - int totalTriangleCount = 0; // size of index array - - // STEP 1 : GenerateInstruction(). Count verts and tris to determine array sizes. - var drawOrderItems = skeleton.drawOrder.Items; - int drawOrderCount = skeleton.drawOrder.Count; - for (int i = 0; i < drawOrderCount; i++) { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.attachment; - int attachmentVertexCount, attachmentTriangleCount; - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - attachmentVertexCount = 4; - attachmentTriangleCount = 6; - } else { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - attachmentVertexCount = meshAttachment.worldVerticesLength >> 1; - attachmentTriangleCount = meshAttachment.triangles.Length; - } else { - continue; - } - } - totalTriangleCount += attachmentTriangleCount; - totalVertexCount += attachmentVertexCount; - } - - // STEP 2 : Ensure buffers are the correct size - ArraysMeshGenerator.EnsureSize(totalVertexCount, ref this.meshVertices, ref this.meshUVs, ref this.meshColors32); - if (addBlackTint) { - ArraysMeshGenerator.EnsureSize(totalVertexCount, ref this.uv2); - ArraysMeshGenerator.EnsureSize(totalVertexCount, ref this.uv3); - } - - this.triangles = this.triangles ?? new int[totalTriangleCount]; - - // STEP 3 : Update vertex buffer - const float zFauxHalfThickness = 0.01f; // Somehow needs this thickness for bounds to work properly in some cases (eg, Unity UI clipping) - Vector3 meshBoundsMin; - Vector3 meshBoundsMax; - if (totalVertexCount == 0) { - meshBoundsMin = new Vector3(0, 0, 0); - meshBoundsMax = new Vector3(0, 0, 0); - } else { - meshBoundsMin.x = int.MaxValue; - meshBoundsMin.y = int.MaxValue; - meshBoundsMax.x = int.MinValue; - meshBoundsMax.y = int.MinValue; - meshBoundsMin.z = -zFauxHalfThickness * scale; - meshBoundsMax.z = zFauxHalfThickness * scale; - - int vertexIndex = 0; - if (addBlackTint) ArraysMeshGenerator.FillBlackUVs(skeleton, 0, drawOrderCount, this.uv2, this.uv3, vertexIndex); - ArraysMeshGenerator.FillVerts(skeleton, 0, drawOrderCount, this.ZSpacing, this.PremultiplyVertexColors, this.meshVertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax); - - // Apply scale to vertices - meshBoundsMax.x *= scale; meshBoundsMax.y *= scale; - meshBoundsMin.x *= scale; meshBoundsMin.y *= scale; - var vertices = this.meshVertices; - for (int i = 0; i < totalVertexCount; i++) { - Vector3 p = vertices[i]; - p.x *= scale; - p.y *= scale; - vertices[i] = p; - } - } - - // Step 4 : Update Triangles buffer - ArraysMeshGenerator.FillTriangles(ref this.triangles, skeleton, totalTriangleCount, 0, 0, drawOrderCount, true); - - // Step 5 : Update Mesh with buffers - var mesh = doubleBufferedMesh.GetNextMesh(); - mesh.vertices = this.meshVertices; - mesh.colors32 = meshColors32; - mesh.uv = meshUVs; - mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax); - mesh.triangles = triangles; - if (addBlackTint) { - mesh.uv2 = this.uv2; - mesh.uv3 = this.uv3; - } - - TryAddNormalsTo(mesh, totalVertexCount); - - if (addTangents) { - SolveTangents2DEnsureSize(ref this.meshTangents, ref this.tempTanBuffer, totalVertexCount); - SolveTangents2DTriangles(this.tempTanBuffer, triangles, totalTriangleCount, meshVertices, meshUVs, totalVertexCount); - SolveTangents2DBuffer(this.meshTangents, this.tempTanBuffer, totalVertexCount); - } - - lastGeneratedMesh = mesh; - return mesh; - } - - } - -} diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshSetMeshGenerator.cs b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshSetMeshGenerator.cs index 90f60d8b5..e69de29bb 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshSetMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshSetMeshGenerator.cs @@ -1,232 +0,0 @@ -/****************************************************************************** - * Spine Runtimes Software License v2.5 - * - * Copyright (c) 2013-2016, Esoteric Software - * All rights reserved. - * - * You are granted a perpetual, non-exclusive, non-sublicensable, and - * non-transferable license to use, install, execute, and perform the Spine - * Runtimes software and derivative works solely for personal or internal - * use. Without the written permission of Esoteric Software (see Section 2 of - * the Spine Software License Agreement), you may not (a) modify, translate, - * adapt, or develop new applications using the Spine Runtimes or otherwise - * create derivative works or improvements of the Spine Runtimes or (b) remove, - * delete, alter, or obscure any trademarks or any copyright, trademark, patent, - * or other intellectual property or proprietary rights notices on or in the - * Software, including any copy thereof. Redistributions in binary or source - * form must include this license and terms. - * - * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF - * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -using UnityEngine; - -namespace Spine.Unity.MeshGeneration { - public class ArraysSubmeshSetMeshGenerator : ArraysMeshGenerator, ISubmeshSetMeshGenerator { - #region Settings - public float ZSpacing { get; set; } - #endregion - - readonly DoubleBuffered doubleBufferedSmartMesh = new DoubleBuffered(); - readonly ExposedList currentInstructions = new ExposedList(); - readonly ExposedList attachmentBuffer = new ExposedList(); - readonly ExposedList submeshBuffers = new ExposedList(); - Material[] sharedMaterials = new Material[0]; - - /// - /// Generates a mesh based on a subset of instructions. - /// - /// A UnityEngine.Mesh. - /// A list of SubmeshInstructions. - /// The index of the starting submesh. - /// The exclusive upper bound of the last submesh to be included. - public MeshAndMaterials GenerateMesh (ExposedList instructions, int startSubmesh, int endSubmesh, float scale = 1f) { - // STEP 0: Prepare instructions. - var paramItems = instructions.Items; - currentInstructions.Clear(false); - for (int i = startSubmesh, n = endSubmesh; i < n; i++) { - this.currentInstructions.Add(paramItems[i]); - } - var smartMesh = doubleBufferedSmartMesh.GetNext(); - var mesh = smartMesh.mesh; - int submeshCount = currentInstructions.Count; - var currentInstructionsItems = currentInstructions.Items; - int vertexCount = 0; - for (int i = 0; i < submeshCount; i++) { - currentInstructionsItems[i].firstVertexIndex = vertexCount;// Ensure current instructions have correct cached values. - vertexCount += currentInstructionsItems[i].vertexCount; // vertexCount will also be used for the rest of this method. - } - - // STEP 1: Ensure correct buffer sizes. - bool vertBufferResized = ArraysMeshGenerator.EnsureSize(vertexCount, ref this.meshVertices, ref this.meshUVs, ref this.meshColors32); - bool submeshBuffersResized = ArraysMeshGenerator.EnsureTriangleBuffersSize(submeshBuffers, submeshCount, currentInstructionsItems); - if (addBlackTint) { - ArraysMeshGenerator.EnsureSize(vertexCount, ref this.uv2); - ArraysMeshGenerator.EnsureSize(vertexCount, ref this.uv3); - } - - // STEP 2: Update buffers based on Skeleton. - - // Initial values for manual Mesh Bounds calculation - Vector3 meshBoundsMin; - Vector3 meshBoundsMax; - float zSpacing = this.ZSpacing; - if (vertexCount <= 0) { - meshBoundsMin = new Vector3(0, 0, 0); - meshBoundsMax = new Vector3(0, 0, 0); - } else { - meshBoundsMin.x = int.MaxValue; - meshBoundsMin.y = int.MaxValue; - meshBoundsMax.x = int.MinValue; - meshBoundsMax.y = int.MinValue; - - int endSlot = currentInstructionsItems[submeshCount - 1].endSlot; - if (zSpacing > 0f) { - meshBoundsMin.z = 0f; - meshBoundsMax.z = zSpacing * endSlot; - } else { - meshBoundsMin.z = zSpacing * endSlot; - meshBoundsMax.z = 0f; - } - } - - // For each submesh, add vertex data from attachments. - var workingAttachments = this.attachmentBuffer; - workingAttachments.Clear(false); - int vertexIndex = 0; // modified by FillVerts - for (int submeshIndex = 0; submeshIndex < submeshCount; submeshIndex++) { - var currentInstruction = currentInstructionsItems[submeshIndex]; - int startSlot = currentInstruction.startSlot; - int endSlot = currentInstruction.endSlot; - var skeleton = currentInstruction.skeleton; - var skeletonDrawOrderItems = skeleton.DrawOrder.Items; - for (int i = startSlot; i < endSlot; i++) { - var ca = skeletonDrawOrderItems[i].attachment; - if (ca != null) workingAttachments.Add(ca); // Includes BoundingBoxes. This is ok. - } - if (addBlackTint) ArraysMeshGenerator.FillBlackUVs(skeleton, startSlot, endSlot, this.uv2, this.uv3, vertexIndex); - ArraysMeshGenerator.FillVerts(skeleton, startSlot, endSlot, zSpacing, this.PremultiplyVertexColors, this.meshVertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax); - } - - bool structureDoesntMatch = vertBufferResized || submeshBuffersResized || smartMesh.StructureDoesntMatch(workingAttachments, currentInstructions); - for (int submeshIndex = 0; submeshIndex < submeshCount; submeshIndex++) { - var currentInstruction = currentInstructionsItems[submeshIndex]; - if (structureDoesntMatch) { - var currentBuffer = submeshBuffers.Items[submeshIndex]; - bool isLastSubmesh = (submeshIndex == submeshCount - 1); - ArraysMeshGenerator.FillTriangles(ref currentBuffer.triangles, currentInstruction.skeleton, currentInstruction.triangleCount, currentInstruction.firstVertexIndex, currentInstruction.startSlot, currentInstruction.endSlot, isLastSubmesh); - currentBuffer.triangleCount = currentInstruction.triangleCount; - currentBuffer.firstVertex = currentInstruction.firstVertexIndex; - } - } - - if (structureDoesntMatch) { - mesh.Clear(); - this.sharedMaterials = currentInstructions.GetUpdatedMaterialArray(this.sharedMaterials); - } - - if (scale != 1f) { - for (int i = 0; i < vertexCount; i++) { - meshVertices[i].x *= scale; - meshVertices[i].y *= scale; - //meshVertices[i].z *= scale; - } - } - - // STEP 3: Assign the buffers into the Mesh. - smartMesh.Set(this.meshVertices, this.meshUVs, this.meshColors32, workingAttachments, currentInstructions); - mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax); - if (addBlackTint) { - mesh.uv2 = this.uv2; - mesh.uv3 = this.uv3; - } - - if (structureDoesntMatch) { - // Push new triangles if doesn't match. - mesh.subMeshCount = submeshCount; - for (int i = 0; i < submeshCount; i++) - mesh.SetTriangles(submeshBuffers.Items[i].triangles, i); - - this.TryAddNormalsTo(mesh, vertexCount); - } - - if (addTangents) { - SolveTangents2DEnsureSize(ref this.meshTangents, ref this.tempTanBuffer, vertexCount); - - for (int i = 0, n = submeshCount; i < n; i++) { - var submesh = submeshBuffers.Items[i]; - SolveTangents2DTriangles(this.tempTanBuffer, submesh.triangles, submesh.triangleCount, meshVertices, meshUVs, vertexCount); - } - - SolveTangents2DBuffer(this.meshTangents, this.tempTanBuffer, vertexCount); - } - - return new MeshAndMaterials(smartMesh.mesh, sharedMaterials); - } - - #region Types - // A SmartMesh is a Mesh (with submeshes) that knows what attachments and instructions were used to generate it. - class SmartMesh { - public readonly Mesh mesh = SpineMesh.NewMesh(); - readonly ExposedList attachmentsUsed = new ExposedList(); - readonly ExposedList instructionsUsed = new ExposedList(); - - public void Set (Vector3[] verts, Vector2[] uvs, Color32[] colors, ExposedList attachments, ExposedList instructions) { - mesh.vertices = verts; - mesh.uv = uvs; - mesh.colors32 = colors; - - attachmentsUsed.Clear(false); - attachmentsUsed.GrowIfNeeded(attachments.Capacity); - attachmentsUsed.Count = attachments.Count; - attachments.CopyTo(attachmentsUsed.Items); - - instructionsUsed.Clear(false); - instructionsUsed.GrowIfNeeded(instructions.Capacity); - instructionsUsed.Count = instructions.Count; - instructions.CopyTo(instructionsUsed.Items); - } - - public bool StructureDoesntMatch (ExposedList attachments, ExposedList instructions) { - // Check count inequality. - if (attachments.Count != this.attachmentsUsed.Count) return true; - if (instructions.Count != this.instructionsUsed.Count) return true; - - // Check each attachment. - var attachmentsPassed = attachments.Items; - var myAttachments = this.attachmentsUsed.Items; - for (int i = 0, n = attachmentsUsed.Count; i < n; i++) - if (attachmentsPassed[i] != myAttachments[i]) return true; - - // Check each submesh for equal arrangement. - var instructionListItems = instructions.Items; - var myInstructions = this.instructionsUsed.Items; - for (int i = 0, n = this.instructionsUsed.Count; i < n; i++) { - var lhs = instructionListItems[i]; - var rhs = myInstructions[i]; - if ( - lhs.material.GetInstanceID() != rhs.material.GetInstanceID() || - lhs.startSlot != rhs.startSlot || - lhs.endSlot != rhs.endSlot || - lhs.triangleCount != rhs.triangleCount || - lhs.vertexCount != rhs.vertexCount || - lhs.firstVertexIndex != rhs.firstVertexIndex - ) return true; - } - - return false; - } - } - #endregion - } - -} diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshedMeshGenerator.cs b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshedMeshGenerator.cs index 4edaccaf8..e69de29bb 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshedMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshedMeshGenerator.cs @@ -1,315 +0,0 @@ -/****************************************************************************** - * Spine Runtimes Software License v2.5 - * - * Copyright (c) 2013-2016, Esoteric Software - * All rights reserved. - * - * You are granted a perpetual, non-exclusive, non-sublicensable, and - * non-transferable license to use, install, execute, and perform the Spine - * Runtimes software and derivative works solely for personal or internal - * use. Without the written permission of Esoteric Software (see Section 2 of - * the Spine Software License Agreement), you may not (a) modify, translate, - * adapt, or develop new applications using the Spine Runtimes or otherwise - * create derivative works or improvements of the Spine Runtimes or (b) remove, - * delete, alter, or obscure any trademarks or any copyright, trademark, patent, - * or other intellectual property or proprietary rights notices on or in the - * Software, including any copy thereof. Redistributions in binary or source - * form must include this license and terms. - * - * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF - * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -using UnityEngine; -using System.Collections.Generic; - -namespace Spine.Unity.MeshGeneration { - /// - /// Arrays submeshed mesh generator. - /// - public class ArraysSubmeshedMeshGenerator : ArraysMeshGenerator, ISubmeshedMeshGenerator, System.IDisposable { - - readonly List separators = new List(); - public List Separators { get { return this.separators; } } - - #region Settings - public float ZSpacing { get; set; } - #endregion - - readonly DoubleBuffered doubleBufferedSmartMesh = new DoubleBuffered(); - readonly SubmeshedMeshInstruction currentInstructions = new SubmeshedMeshInstruction(); - readonly ExposedList submeshBuffers = new ExposedList(); - Material[] sharedMaterials = new Material[0]; - - public void Dispose () { - doubleBufferedSmartMesh.GetNext().Dispose(); - doubleBufferedSmartMesh.GetNext().Dispose(); - } - - public SubmeshedMeshInstruction GenerateInstruction (Skeleton skeleton) { - if (skeleton == null) throw new System.ArgumentNullException("skeleton"); - - // Count vertices and submesh triangles. - int runningVertexCount = 0; - - int submeshTriangleCount = 0; - int submeshFirstVertex = 0; - int submeshVertexCount = 0; - int submeshStartSlotIndex = 0; - Material lastMaterial = null; - - var drawOrder = skeleton.drawOrder; - var drawOrderItems = drawOrder.Items; - int drawOrderCount = drawOrder.Count; - int separatorCount = separators.Count; - - var instructionList = this.currentInstructions.submeshInstructions; - instructionList.Clear(false); - - currentInstructions.attachmentList.Clear(false); - - for (int i = 0; i < drawOrderCount; i++) { - var slot = drawOrderItems[i]; - var attachment = slot.attachment; - - object rendererObject; // An AtlasRegion in plain Spine-Unity. eventual source of Material object. - int attachmentVertexCount, attachmentTriangleCount; - - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - rendererObject = regionAttachment.RendererObject; - attachmentVertexCount = 4; - attachmentTriangleCount = 6; - } else { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - rendererObject = meshAttachment.RendererObject; - attachmentVertexCount = meshAttachment.worldVerticesLength >> 1; - attachmentTriangleCount = meshAttachment.triangles.Length; - } else { - continue; - } - } - - var attachmentMaterial = (Material)((AtlasRegion)rendererObject).page.rendererObject; - - // Populate submesh when material changes. (or when forced to separate by a submeshSeparator) - bool separatedBySlot = ( separatorCount > 0 && separators.Contains(slot) ); - if (( runningVertexCount > 0 && lastMaterial.GetInstanceID() != attachmentMaterial.GetInstanceID() ) || separatedBySlot) { - - instructionList.Add( - new SubmeshInstruction { - skeleton = skeleton, - material = lastMaterial, - triangleCount = submeshTriangleCount, - vertexCount = submeshVertexCount, - startSlot = submeshStartSlotIndex, - endSlot = i, - firstVertexIndex = submeshFirstVertex, - forceSeparate = separatedBySlot - } - ); - - // Prepare for next submesh - submeshTriangleCount = 0; - submeshVertexCount = 0; - submeshFirstVertex = runningVertexCount; - submeshStartSlotIndex = i; - } - lastMaterial = attachmentMaterial; - - submeshTriangleCount += attachmentTriangleCount; - submeshVertexCount += attachmentVertexCount; - runningVertexCount += attachmentVertexCount; - - currentInstructions.attachmentList.Add(attachment); - } - - instructionList.Add( - new SubmeshInstruction { - skeleton = skeleton, - material = lastMaterial, - triangleCount = submeshTriangleCount, - vertexCount = submeshVertexCount, - startSlot = submeshStartSlotIndex, - endSlot = drawOrderCount, - firstVertexIndex = submeshFirstVertex, - forceSeparate = false - } - ); - - currentInstructions.vertexCount = runningVertexCount; - return currentInstructions; - } - - // ISubmeshedMeshGenerator.GenerateMesh - /// Generates a mesh based on SubmeshedMeshInstructions - public MeshAndMaterials GenerateMesh (SubmeshedMeshInstruction meshInstructions) { - var smartMesh = doubleBufferedSmartMesh.GetNext(); - var mesh = smartMesh.mesh; - int submeshCount = meshInstructions.submeshInstructions.Count; - var instructionList = meshInstructions.submeshInstructions; - - // STEP 1: Ensure correct buffer sizes. - int vertexCount = meshInstructions.vertexCount; - bool submeshBuffersResized = ArraysMeshGenerator.EnsureTriangleBuffersSize(submeshBuffers, submeshCount, instructionList.Items); - bool vertBufferResized = ArraysMeshGenerator.EnsureSize(vertexCount, ref this.meshVertices, ref this.meshUVs, ref this.meshColors32); - Vector3[] vertices = this.meshVertices; - - if (addBlackTint) { - ArraysMeshGenerator.EnsureSize(vertexCount, ref this.uv2); - ArraysMeshGenerator.EnsureSize(vertexCount, ref this.uv3); - } - - // STEP 2: Update buffers based on Skeleton. - float zSpacing = this.ZSpacing; - Vector3 meshBoundsMin; - Vector3 meshBoundsMax; - int attachmentCount = meshInstructions.attachmentList.Count; - if (attachmentCount <= 0) { - meshBoundsMin = new Vector3(0, 0, 0); - meshBoundsMax = new Vector3(0, 0, 0); - } else { - meshBoundsMin.x = int.MaxValue; - meshBoundsMin.y = int.MaxValue; - meshBoundsMax.x = int.MinValue; - meshBoundsMax.y = int.MinValue; - - if (zSpacing > 0f) { - meshBoundsMin.z = 0f; - meshBoundsMax.z = zSpacing * (attachmentCount - 1); - } else { - meshBoundsMin.z = zSpacing * (attachmentCount - 1); - meshBoundsMax.z = 0f; - } - } - bool structureDoesntMatch = vertBufferResized || submeshBuffersResized || smartMesh.StructureDoesntMatch(meshInstructions); - // For each submesh, add vertex data from attachments. Also triangles, but only if needed. - int vertexIndex = 0; // modified by FillVerts - for (int submeshIndex = 0; submeshIndex < submeshCount; submeshIndex++) { - var submeshInstruction = instructionList.Items[submeshIndex]; - int start = submeshInstruction.startSlot; - int end = submeshInstruction.endSlot; - var skeleton = submeshInstruction.skeleton; - if (addBlackTint) ArraysMeshGenerator.FillBlackUVs(skeleton, start, end, this.uv2, this.uv3, vertexIndex); - ArraysMeshGenerator.FillVerts(skeleton, start, end, zSpacing, this.PremultiplyVertexColors, vertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax); - - if (structureDoesntMatch) { - var currentBuffer = submeshBuffers.Items[submeshIndex]; - bool isLastSubmesh = (submeshIndex == submeshCount - 1); - ArraysMeshGenerator.FillTriangles(ref currentBuffer.triangles, skeleton, submeshInstruction.triangleCount, submeshInstruction.firstVertexIndex, start, end, isLastSubmesh); - currentBuffer.triangleCount = submeshInstruction.triangleCount; - currentBuffer.firstVertex = submeshInstruction.firstVertexIndex; - } - } - - if (structureDoesntMatch) { - mesh.Clear(); - this.sharedMaterials = meshInstructions.GetUpdatedMaterialArray(this.sharedMaterials); - } - - // STEP 3: Assign the buffers into the Mesh. - smartMesh.Set(this.meshVertices, this.meshUVs, this.meshColors32, meshInstructions); - if (addBlackTint) { - mesh.uv2 = this.uv2; - mesh.uv3 = this.uv3; - } - mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax); - - if (structureDoesntMatch) { - // Push new triangles if doesn't match. - mesh.subMeshCount = submeshCount; - for (int i = 0; i < submeshCount; i++) - mesh.SetTriangles(submeshBuffers.Items[i].triangles, i); - - TryAddNormalsTo(mesh, vertexCount); - } - - if (addTangents) { - SolveTangents2DEnsureSize(ref this.meshTangents, ref this.tempTanBuffer, vertexCount); - for (int i = 0, n = submeshCount; i < n; i++) { - var submesh = submeshBuffers.Items[i]; - SolveTangents2DTriangles(this.tempTanBuffer, submesh.triangles, submesh.triangleCount, meshVertices, meshUVs, vertexCount); - } - SolveTangents2DBuffer(this.meshTangents, this.tempTanBuffer, vertexCount); - } - - return new MeshAndMaterials(smartMesh.mesh, sharedMaterials); - } - - #region Types - // A SmartMesh is a Mesh (with submeshes) that knows what attachments and instructions were used to generate it. - class SmartMesh : System.IDisposable { - public readonly Mesh mesh = SpineMesh.NewMesh(); - readonly ExposedList attachmentsUsed = new ExposedList(); - readonly ExposedList instructionsUsed = new ExposedList(); - - public void Dispose () { - if (mesh != null) { - if (Application.isEditor && !Application.isPlaying) { - UnityEngine.Object.DestroyImmediate(mesh); - } else { - UnityEngine.Object.Destroy(mesh); - } - } - } - - public void Set (Vector3[] verts, Vector2[] uvs, Color32[] colors, SubmeshedMeshInstruction instruction) { - mesh.vertices = verts; - mesh.uv = uvs; - mesh.colors32 = colors; - - attachmentsUsed.Clear(false); - attachmentsUsed.GrowIfNeeded(instruction.attachmentList.Capacity); - attachmentsUsed.Count = instruction.attachmentList.Count; - instruction.attachmentList.CopyTo(attachmentsUsed.Items); - - instructionsUsed.Clear(false); - instructionsUsed.GrowIfNeeded(instruction.submeshInstructions.Capacity); - instructionsUsed.Count = instruction.submeshInstructions.Count; - instruction.submeshInstructions.CopyTo(instructionsUsed.Items); - } - - public bool StructureDoesntMatch (SubmeshedMeshInstruction instructions) { - // Check count inequality. - if (instructions.attachmentList.Count != this.attachmentsUsed.Count) return true; - if (instructions.submeshInstructions.Count != this.instructionsUsed.Count) return true; - - // Check each attachment. - var attachmentsPassed = instructions.attachmentList.Items; - var myAttachments = this.attachmentsUsed.Items; - for (int i = 0, n = attachmentsUsed.Count; i < n; i++) - if (attachmentsPassed[i] != myAttachments[i]) return true; - - // Check each submesh for equal arrangement. - var instructionListItems = instructions.submeshInstructions.Items; - var myInstructions = this.instructionsUsed.Items; - for (int i = 0, n = this.instructionsUsed.Count; i < n; i++) { - var lhs = instructionListItems[i]; - var rhs = myInstructions[i]; - if ( - lhs.material.GetInstanceID() != rhs.material.GetInstanceID() || - lhs.startSlot != rhs.startSlot || - lhs.endSlot != rhs.endSlot || - lhs.triangleCount != rhs.triangleCount || - lhs.vertexCount != rhs.vertexCount || - lhs.firstVertexIndex != rhs.firstVertexIndex - ) return true; - } - - //Debug.Log("structure matched"); - return false; - } - } - #endregion - } - -} diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/DoubleBufferedMesh.cs b/spine-unity/Assets/spine-unity/Mesh Generation/DoubleBufferedMesh.cs index 87e0f119a..e69de29bb 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/DoubleBufferedMesh.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/DoubleBufferedMesh.cs @@ -1,45 +0,0 @@ -/****************************************************************************** - * Spine Runtimes Software License v2.5 - * - * Copyright (c) 2013-2016, Esoteric Software - * All rights reserved. - * - * You are granted a perpetual, non-exclusive, non-sublicensable, and - * non-transferable license to use, install, execute, and perform the Spine - * Runtimes software and derivative works solely for personal or internal - * use. Without the written permission of Esoteric Software (see Section 2 of - * the Spine Software License Agreement), you may not (a) modify, translate, - * adapt, or develop new applications using the Spine Runtimes or otherwise - * create derivative works or improvements of the Spine Runtimes or (b) remove, - * delete, alter, or obscure any trademarks or any copyright, trademark, patent, - * or other intellectual property or proprietary rights notices on or in the - * Software, including any copy thereof. Redistributions in binary or source - * form must include this license and terms. - * - * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF - * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -using UnityEngine; -using System.Collections; - -namespace Spine.Unity { - public class DoubleBufferedMesh { - readonly Mesh mesh1 = SpineMesh.NewMesh(); - readonly Mesh mesh2 = SpineMesh.NewMesh(); - bool usingMesh1; - - public Mesh GetNextMesh () { - usingMesh1 = !usingMesh1; - return usingMesh1 ? mesh1 : mesh2; - } - } -} diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/ISimpleMeshGenerator.cs b/spine-unity/Assets/spine-unity/Mesh Generation/ISimpleMeshGenerator.cs index fc440cc5f..e69de29bb 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/ISimpleMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/ISimpleMeshGenerator.cs @@ -1,48 +0,0 @@ -/****************************************************************************** - * Spine Runtimes Software License v2.5 - * - * Copyright (c) 2013-2016, Esoteric Software - * All rights reserved. - * - * You are granted a perpetual, non-exclusive, non-sublicensable, and - * non-transferable license to use, install, execute, and perform the Spine - * Runtimes software and derivative works solely for personal or internal - * use. Without the written permission of Esoteric Software (see Section 2 of - * the Spine Software License Agreement), you may not (a) modify, translate, - * adapt, or develop new applications using the Spine Runtimes or otherwise - * create derivative works or improvements of the Spine Runtimes or (b) remove, - * delete, alter, or obscure any trademarks or any copyright, trademark, patent, - * or other intellectual property or proprietary rights notices on or in the - * Software, including any copy thereof. Redistributions in binary or source - * form must include this license and terms. - * - * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF - * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -namespace Spine.Unity.MeshGeneration { - // Typically, each ISpineMeshGenerator implementation will handle double-buffering meshes, handling any other optimization behavior - // and operating on assumptions (eg, only handling one skeleton, not updating triangles all the time). - // The Scale property allows generated mesh to match external systems like Canvas referencePixelsPerUnit - - public interface ISimpleMeshGenerator { - UnityEngine.Mesh GenerateMesh (Spine.Skeleton skeleton); - UnityEngine.Mesh LastGeneratedMesh { get; } - - float Scale { set; } - float ZSpacing { get; set; } - bool PremultiplyVertexColors { get; set; } - - bool AddNormals { get; set; } - bool AddTangents { get; set; } - bool AddBlackTint { get; set; } - } -} diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/ISubmeshedMeshGenerator.cs b/spine-unity/Assets/spine-unity/Mesh Generation/ISubmeshedMeshGenerator.cs index dda27ccec..e69de29bb 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/ISubmeshedMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/ISubmeshedMeshGenerator.cs @@ -1,125 +0,0 @@ -/****************************************************************************** - * Spine Runtimes Software License v2.5 - * - * Copyright (c) 2013-2016, Esoteric Software - * All rights reserved. - * - * You are granted a perpetual, non-exclusive, non-sublicensable, and - * non-transferable license to use, install, execute, and perform the Spine - * Runtimes software and derivative works solely for personal or internal - * use. Without the written permission of Esoteric Software (see Section 2 of - * the Spine Software License Agreement), you may not (a) modify, translate, - * adapt, or develop new applications using the Spine Runtimes or otherwise - * create derivative works or improvements of the Spine Runtimes or (b) remove, - * delete, alter, or obscure any trademarks or any copyright, trademark, patent, - * or other intellectual property or proprietary rights notices on or in the - * Software, including any copy thereof. Redistributions in binary or source - * form must include this license and terms. - * - * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF - * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER - * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - *****************************************************************************/ - -using UnityEngine; -using System.Collections.Generic; - -namespace Spine.Unity.MeshGeneration { - // ISubmeshedMeshGenerator: - // How to use: - // Step 1: Have a SubmeshedMeshGenerator instance, and a Spine.Skeleton - // Step 2: Call GenerateInstruction. Pass it your Skeleton. Keep the return value (a SubmeshedMeshInstruction, you can use it in other classes too). - // Step 3: Pass the SubmeshedMeshInstruction into GenerateMesh. You'll get a Mesh and Materials. - // Step 4: Put the Mesh in MeshFilter. Put the Materials in MeshRenderer.sharedMaterials. - public interface ISubmeshedMeshGenerator { - SubmeshedMeshInstruction GenerateInstruction (Skeleton skeleton); - MeshAndMaterials GenerateMesh (SubmeshedMeshInstruction wholeMeshInstruction); - List Separators { get; } - - float ZSpacing { get; set; } - bool PremultiplyVertexColors { get; set; } - bool AddNormals { get; set; } - bool AddTangents { get; set; } - bool AddBlackTint { get; set; } - } - - // ISubmeshSetMeshGenerator - // How to use: - // Step 1: Get a list of SubmeshInstruction. You can get this from SkeletonRenderer or an ISubmeshedMeshGenerator's returned SubmeshedMeshInstruction. - // Step 2: Call AddInstruction one by one, or AddInstructions once. - // Step 3: Call GenerateMesh. You'll get a Mesh and Materials. - // Step 4: Put the Mesh in MeshFilter. Put the Materials in MeshRenderer.sharedMaterials. - public interface ISubmeshSetMeshGenerator { - MeshAndMaterials GenerateMesh (ExposedList instructions, int startSubmesh, int endSubmesh, float scale = 1f); - - float ZSpacing { get; set; } - bool PremultiplyVertexColors { get; set; } - bool AddNormals { get; set; } - bool AddTangents { get; set; } - bool AddBlackTint { get; set; } - } - - /// Primarily a collection of Submesh Instructions. This constitutes instructions for how to construct a mesh containing submeshes. - public class SubmeshedMeshInstruction { - public readonly ExposedList submeshInstructions = new ExposedList(); - public readonly ExposedList attachmentList = new ExposedList(); - public int vertexCount = -1; - - /// Returns a material array of the SubmeshedMeshInstruction. Fills the passed array if it's the correct size. Creates a new array if it's a different size. - public Material[] GetUpdatedMaterialArray (Material[] materials) { - return submeshInstructions.GetUpdatedMaterialArray(materials); - } - } - - /// Instructions for how to generate a mesh or submesh out of a range of slots in a given skeleton. - public struct SubmeshInstruction { - public Skeleton skeleton; - public int startSlot; - public int endSlot; - - // Cached values because they are determined in the process of generating instructions, - // but could otherwise be pulled from accessing attachments, checking materials and counting tris and verts. - public Material material; - public int triangleCount; - public int vertexCount; - - // Vertex index offset. Used by submesh generation if part of a bigger mesh. - public int firstVertexIndex; - public bool forceSeparate; - - /// The number of slots in this SubmeshInstruction's range. Not necessarily the number of attachments. - public int SlotCount { get { return endSlot - startSlot; } } - } - - public static class SubmeshInstructionExtensions { - /// Returns a material array of the instructions. Fills the passed array if it's the correct size. Creates a new array if it's a different size. - public static Material[] GetUpdatedMaterialArray (this ExposedList instructions, Material[] materials) { - int submeshCount = instructions.Count; - - if (submeshCount != materials.Length) - materials = new Material[submeshCount]; - - for (int i = 0, n = materials.Length; i < n; i++) - materials[i] = instructions.Items[i].material; - - return materials; - } - } - - public struct MeshAndMaterials { - public readonly Mesh mesh; - public readonly Material[] materials; - - public MeshAndMaterials (Mesh mesh, Material[] materials) { - this.mesh = mesh; - this.materials = materials; - } - } -} diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs b/spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs index d2186d974..9278cc940 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/SpineMesh.cs @@ -28,11 +28,14 @@ * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +//#define SPINE_TRIANGLECHECK // Avoid calling SetTriangles at the cost of checking for mesh differences (vertex counts, memberwise attachment list compare) every frame. + using UnityEngine; +using System; +using System.Collections.Generic; namespace Spine.Unity { public static class SpineMesh { - internal const HideFlags MeshHideflags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor; /// Factory method for creating a new mesh for use in Spine components. This can be called in field initializers. @@ -44,4 +47,1123 @@ namespace Spine.Unity { return m; } } + + /// Instructions for how to generate a mesh or submesh out of a range of slots in a given skeleton. + public struct SubmeshInstruction { + public Skeleton skeleton; + public int startSlot; + public int endSlot; + + public Material material; + public bool forceSeparate; + public int preActiveClippingSlotSource; + + #if SPINE_TRIANGLECHECK + // Cached values because they are determined in the process of generating instructions, + // but could otherwise be pulled from accessing attachments, checking materials and counting tris and verts. + public int rawTriangleCount; + public int rawVertexCount; + public int rawFirstVertexIndex; + public bool hasClipping; + #endif + + /// The number of slots in this SubmeshInstruction's range. Not necessarily the number of attachments. + public int SlotCount { get { return endSlot - startSlot; } } + } + + [System.Serializable] + public class MeshGenerator { + public Settings settings = Settings.Default; + + [System.Serializable] + public struct Settings { + public bool renderMeshes; + public bool useClipping; + [Space] + [Range(-0.1f, 0f)] public float zSpacing; + [Space] + [Header("Vertex Data")] + public bool pmaVertexColors; + public bool tintBlack; + public bool calculateTangents; + public bool addNormals; + public bool immutableTriangles; + + static public Settings Default { + get { + return new Settings { + pmaVertexColors = true, + zSpacing = 0f, + useClipping = true, + tintBlack = false, + calculateTangents = false, + renderMeshes = true, + addNormals = false, + immutableTriangles = false + }; + } + } + } + + const float BoundsMinDefault = float.MaxValue; + const float BoundsMaxDefault = float.MinValue; + + [NonSerialized] readonly ExposedList vertexBuffer = new ExposedList(); + [NonSerialized] readonly ExposedList uvBuffer = new ExposedList(); + [NonSerialized] readonly ExposedList colorBuffer = new ExposedList(); + [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 + [NonSerialized] Vector3[] normals; + [NonSerialized] Vector4[] tangents; + [NonSerialized] Vector2[] tempTanBuffer; + [NonSerialized] ExposedList uv2; + [NonSerialized] ExposedList uv3; + #endregion + + #region Step 1 : Generate Instructions + public static void GenerateSingleSubmeshInstruction (SkeletonRendererInstruction instructionOutput, Skeleton skeleton, bool renderMeshes, 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]; + Attachment attachment = slot.attachment; + + workingAttachmentsItems[i] = attachment; + int attachmentTriangleCount = 0; + int attachmentVertexCount = 0; + + + var regionAttachment = attachment as RegionAttachment; + if (regionAttachment != null) { + attachmentVertexCount = 4; + attachmentTriangleCount = 6; + } else { + if (renderMeshes) { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) { + attachmentVertexCount = meshAttachment.worldVerticesLength >> 1; + attachmentTriangleCount = meshAttachment.triangles.Length; + } else { + var clippingAttachment = attachment as ClippingAttachment; + if (clippingAttachment != null) { + current.hasClipping = true; + skeletonHasClipping = true; + } + attachmentVertexCount = 0; + attachmentTriangleCount = 0; + //continue; + } + } + } + current.rawTriangleCount += attachmentTriangleCount; + current.rawVertexCount += attachmentVertexCount; + totalRawVertexCount += attachmentVertexCount; + + } + + instructionOutput.hasActiveClipping = skeletonHasClipping; + instructionOutput.rawVertexCount = totalRawVertexCount; + #endif + + workingSubmeshInstructions.Items[0] = current; + } + + public static void GenerateSkeletonRendererInstruction (SkeletonRendererInstruction instructionOutput, Skeleton skeleton, Dictionary customSlotMaterials, List separatorSlots, bool generateMeshOverride, bool immutableTriangles = false, bool renderMeshes = true) { +// 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; + SlotData clippingEndSlot = null; + int submeshIndex = 0; + var drawOrderItems = drawOrder.Items; + bool currentHasRenderable = false; + for (int i = 0; i < drawOrderCount; i++) { + Slot slot = drawOrderItems[i]; + Attachment attachment = slot.attachment; + #if SPINE_TRIANGLECHECK + workingAttachmentsItems[i] = attachment; + int attachmentVertexCount = 0, attachmentTriangleCount = 0; + #endif + + object rendererObject = null; // An AtlasRegion in plain Spine-Unity. Spine-TK2D hooks into TK2D's system. eventual source of Material object. + bool noRender = false; // Using this allows empty slots as separators, and keeps separated parts more stable despite slots being reordered + + var regionAttachment = attachment as RegionAttachment; + if (regionAttachment != null) { + rendererObject = regionAttachment.RendererObject; + #if SPINE_TRIANGLECHECK + attachmentVertexCount = 4; + attachmentTriangleCount = 6; + #endif + currentHasRenderable = true; + } else { + if (renderMeshes) { + var meshAttachment = attachment as MeshAttachment; + if (meshAttachment != null) { + rendererObject = meshAttachment.RendererObject; + #if SPINE_TRIANGLECHECK + attachmentVertexCount = meshAttachment.worldVerticesLength >> 1; + attachmentTriangleCount = meshAttachment.triangles.Length; + #endif + currentHasRenderable = true; + } else { + #if SPINE_TRIANGLECHECK + var clippingAttachment = attachment as ClippingAttachment; + if (clippingAttachment != null) { + clippingEndSlot = clippingAttachment.endSlot; + clippingAttachmentSource = i; + current.hasClipping = true; + skeletonHasClipping = true; + } + #endif + noRender = true; + //continue; + } + } else { + noRender = true; + } + } + + if (clippingEndSlot != null && slot.data == clippingEndSlot) { + clippingEndSlot = null; + clippingAttachmentSource = -1; + } + + // Create a new SubmeshInstruction when material changes. (or when forced to separate by a submeshSeparator) + // Slot with a separator/new material will become the starting slot of the next new instruction. + if (hasSeparators) { //current.forceSeparate = hasSeparators && separatorSlots.Contains(slot); + current.forceSeparate = false; + for (int s = 0; s < separatorCount; s++) { + if (Slot.ReferenceEquals(slot, separatorSlots[s])) { + current.forceSeparate = true; + break; + } + } + } + + if (noRender) { + if (current.forceSeparate && currentHasRenderable && generateMeshOverride) { + { // Add + current.endSlot = i; + current.preActiveClippingSlotSource = lastPreActiveClipping; + + workingSubmeshInstructions.Resize(submeshIndex + 1); + workingSubmeshInstructions.Items[submeshIndex] = current; + + submeshIndex++; + } + currentHasRenderable = false; + current.startSlot = i; + lastPreActiveClipping = clippingAttachmentSource; + #if SPINE_TRIANGLECHECK + current.rawTriangleCount = 0; + current.rawVertexCount = 0; + current.rawFirstVertexIndex = totalRawVertexCount; + current.hasClipping = clippingAttachmentSource >= 0; + #endif + } + } else { + #if !SPINE_TK2D + Material material; + if (isCustomSlotMaterialsPopulated) { + if (!customSlotMaterials.TryGetValue(slot, out material)) + material = (Material)((AtlasRegion)rendererObject).page.rendererObject; + } else { + material = (Material)((AtlasRegion)rendererObject).page.rendererObject; + } + #else + Material material = (rendererObject.GetType() == typeof(Material)) ? (Material)rendererObject : (Material)((AtlasRegion)rendererObject).page.rendererObject; + #endif + + if (currentHasRenderable && (current.forceSeparate || !System.Object.ReferenceEquals(current.material, material))) { // Material changed. Add the previous submesh. + { // Add + current.endSlot = i; + current.preActiveClippingSlotSource = lastPreActiveClipping; + + workingSubmeshInstructions.Resize(submeshIndex + 1); + workingSubmeshInstructions.Items[submeshIndex] = current; + submeshIndex++; + } + currentHasRenderable = false; + current.startSlot = i; + lastPreActiveClipping = clippingAttachmentSource; + #if SPINE_TRIANGLECHECK + current.rawTriangleCount = 0; + current.rawVertexCount = 0; + current.rawFirstVertexIndex = totalRawVertexCount; + current.hasClipping = clippingAttachmentSource >= 0; + #endif + } + + // Update state for the next Attachment. + current.material = material; + #if SPINE_TRIANGLECHECK + current.rawTriangleCount += attachmentTriangleCount; + current.rawVertexCount += attachmentVertexCount; + current.rawFirstVertexIndex = totalRawVertexCount; + totalRawVertexCount += attachmentVertexCount; + #endif + } + } + + if (currentHasRenderable) { + { // Add last or only submesh. + current.endSlot = drawOrderCount; + current.preActiveClippingSlotSource = lastPreActiveClipping; + current.forceSeparate = false; + + workingSubmeshInstructions.Resize(submeshIndex + 1); + workingSubmeshInstructions.Items[submeshIndex] = current; + //submeshIndex++; + } + } + + #if SPINE_TRIANGLECHECK + instructionOutput.hasActiveClipping = skeletonHasClipping; + instructionOutput.rawVertexCount = totalRawVertexCount; + #endif + instructionOutput.immutableTriangles = immutableTriangles; + } + + public static void TryReplaceMaterials (ExposedList 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 workingSubmeshInstructionsItems = workingSubmeshInstructions.Items; + for (int i = 0; i < workingSubmeshInstructions.Count; i++) { + var m = workingSubmeshInstructionsItems[i].material; + Material mo; + if (customMaterialOverride.TryGetValue(m, out mo)) + workingSubmeshInstructionsItems[i].material = mo; + } + } + #endregion + + #region Step 2 : Populate vertex data and triangle index buffers. + public void BeginNewMesh () { + vertexBuffer.Clear(false); + colorBuffer.Clear(false); + uvBuffer.Clear(false); + clipper.ClipEnd(); + + { + meshBoundsMin.x = BoundsMinDefault; + meshBoundsMin.y = BoundsMinDefault; + meshBoundsMax.x = BoundsMaxDefault; + meshBoundsMax.y = BoundsMaxDefault; + meshBoundsThickness = 0f; + } + + submeshes.Count = 1; + submeshes.Items[0].Clear(false); + submeshIndex = 0; + } + + public void AddSubmesh (SubmeshInstruction instruction) { + var settings = this.settings; + if (!settings.renderMeshes) { + AddSubmeshQuadsOnly(instruction); + return; + } + + if (submeshes.Count - 1 < submeshIndex) { + submeshes.Resize(submeshIndex + 1); + if (submeshes.Items[submeshIndex] == null) + submeshes.Items[submeshIndex] = new ExposedList(); + } + var submesh = submeshes.Items[submeshIndex]; + submesh.Clear(false); + + var skeleton = instruction.skeleton; + var drawOrderItems = skeleton.drawOrder.Items; + + Color32 color; + float skeletonA = skeleton.a * 255, skeletonR = skeleton.r, skeletonG = skeleton.g, skeletonB = skeleton.b; + Vector2 meshBoundsMin = this.meshBoundsMin, meshBoundsMax = this.meshBoundsMax; + + // Settings + float zSpacing = settings.zSpacing; + #if SPINE_TRIANGLECHECK + bool useClipping = settings.useClipping && instruction.hasClipping; + #else + bool useClipping = settings.useClipping; + #endif + + if (useClipping) { + if (instruction.preActiveClippingSlotSource >= 0) { + Debug.Log("PreActiveClipping"); + var slot = drawOrderItems[instruction.preActiveClippingSlotSource]; + clipper.ClipStart(slot, slot.attachment as ClippingAttachment); + } + } + + bool pmaVertexColors = settings.pmaVertexColors; + for (int slotIndex = instruction.startSlot; slotIndex < instruction.endSlot; slotIndex++) { + var slot = drawOrderItems[slotIndex]; + var attachment = slot.attachment; + float z = zSpacing * slotIndex; + + var workingVerts = this.tempVerts; + float[] uvs; + int[] attachmentTriangleIndices; + int attachmentVertexCount; + int attachmentIndexCount; + + Color c = default(Color); + + var region = attachment as RegionAttachment; + if (region != null) { + region.ComputeWorldVertices(slot.bone, workingVerts, 0); + uvs = region.uvs; + attachmentTriangleIndices = regionTriangles; + c.r = region.r; c.g = region.g; c.b = region.b; c.a = region.a; + attachmentVertexCount = 4; + attachmentIndexCount = 6; + } else { + var mesh = attachment as MeshAttachment; + if (mesh != null) { + int meshVertexCount = mesh.worldVerticesLength; + if (workingVerts.Length < meshVertexCount) { + workingVerts = new float[meshVertexCount]; + this.tempVerts = workingVerts; + } + mesh.ComputeWorldVertices(slot, 0, meshVertexCount, workingVerts, 0); //meshAttachment.ComputeWorldVertices(slot, tempVerts); + uvs = mesh.uvs; + attachmentTriangleIndices = mesh.triangles; + c.r = mesh.r; c.g = mesh.g; c.b = mesh.b; c.a = mesh.a; + attachmentVertexCount = meshVertexCount >> 1; // meshVertexCount / 2; + attachmentIndexCount = mesh.triangles.Length; + } else { + if (useClipping) { + var clippingAttachment = attachment as ClippingAttachment; + if (clippingAttachment != null) { + clipper.ClipStart(slot, clippingAttachment); + continue; + } + } + + continue; + } + } + + if (pmaVertexColors) { + color.a = (byte)(skeletonA * slot.a * c.a); + color.r = (byte)(skeletonR * slot.r * c.r * color.a); + color.g = (byte)(skeletonG * slot.g * c.g * color.a); + color.b = (byte)(skeletonB * slot.b * c.b * color.a); + if (slot.data.blendMode == BlendMode.Additive) color.a = 0; + } else { + color.a = (byte)(skeletonA * slot.a * c.a); + color.r = (byte)(skeletonR * slot.r * c.r * 255); + color.g = (byte)(skeletonG * slot.g * c.g * 255); + color.b = (byte)(skeletonB * slot.b * c.b * 255); + } + + if (useClipping && clipper.IsClipping()) { + clipper.ClipTriangles(workingVerts, attachmentVertexCount << 1, attachmentTriangleIndices, attachmentIndexCount, uvs); + workingVerts = clipper.clippedVertices.Items; + attachmentVertexCount = clipper.clippedVertices.Count >> 1; + attachmentTriangleIndices = clipper.clippedTriangles.Items; + attachmentIndexCount = clipper.clippedTriangles.Count; + uvs = clipper.clippedUVs.Items; + } + + if (attachmentVertexCount != 0 && attachmentIndexCount != 0) { + if (settings.tintBlack) + AddAttachmentTintBlack(slot.r2, slot.g2, slot.b2, attachmentVertexCount); + + //AddAttachment(workingVerts, uvs, color, attachmentTriangleIndices, attachmentVertexCount, attachmentIndexCount, ref meshBoundsMin, ref meshBoundsMax, z); + int ovc = vertexBuffer.Count; + // Add data to vertex buffers + { + int newVertexCount = ovc + attachmentVertexCount; + if (newVertexCount > vertexBuffer.Items.Length) { // Manual ExposedList.Resize() + Array.Resize(ref vertexBuffer.Items, newVertexCount); + Array.Resize(ref uvBuffer.Items, newVertexCount); + Array.Resize(ref colorBuffer.Items, newVertexCount); + } + vertexBuffer.Count = uvBuffer.Count = colorBuffer.Count = newVertexCount; + } + + var vbi = vertexBuffer.Items; + var ubi = uvBuffer.Items; + var cbi = colorBuffer.Items; + for (int i = 0; i < attachmentVertexCount; i++) { + int vi = ovc + i; + int i2 = i << 1; // i * 2 + float x = workingVerts[i2]; + float y = workingVerts[i2 + 1]; + + vbi[vi].x = x; + vbi[vi].y = y; + vbi[vi].z = z; + ubi[vi].x = uvs[i2]; + ubi[vi].y = uvs[i2 + 1]; + cbi[vi] = color; + + // Calculate bounds. + if (x < meshBoundsMin.x) meshBoundsMin.x = x; + else if (x > meshBoundsMax.x) meshBoundsMax.x = x; + if (y < meshBoundsMin.y) meshBoundsMin.y = y; + else if (y > meshBoundsMax.y) meshBoundsMax.y = y; + } + + // Add data to triangle buffer + int oldTriangleCount = submesh.Count; + { //submesh.Resize(oldTriangleCount + attachmentIndexCount); + int newTriangleCount = oldTriangleCount + attachmentIndexCount; + if (newTriangleCount > submesh.Items.Length) Array.Resize(ref submesh.Items, newTriangleCount); + submesh.Count = newTriangleCount; + } + var submeshItems = submesh.Items; + for (int i = 0; i < attachmentIndexCount; i++) + submeshItems[oldTriangleCount + i] = attachmentTriangleIndices[i] + ovc; + } + + clipper.ClipEnd(slot); + } + clipper.ClipEnd(); + + this.meshBoundsMin = meshBoundsMin; + this.meshBoundsMax = meshBoundsMax; + meshBoundsThickness = instruction.endSlot * zSpacing; + + // Trim or zero submesh triangles. + var currentSubmeshItems = submesh.Items; + for (int i = submesh.Count, n = currentSubmeshItems.Length; i < n; i++) + currentSubmeshItems[i] = 0; + + // Next AddSubmesh will use a new submeshIndex value. + submeshIndex++; + } + + public void ScaleVertexData (float scale) { + var vbi = vertexBuffer.Items; + for (int i = 0, n = vertexBuffer.Count; i < n; i++) { +// vbi[i].x *= scale; +// vbi[i].y *= scale; + vbi[i] *= scale; + } + + meshBoundsMin *= scale; + meshBoundsMax *= scale; + meshBoundsThickness *= scale; + } + + void AddSubmeshQuadsOnly (SubmeshInstruction instruction) { + const int attachmentVertexCount = 4; + const int attachmentIndexCount = 6; + int[] attachmentTriangleIndices = regionTriangles; + + var settings = this.settings; + + if (submeshes.Count - 1 < submeshIndex) { + submeshes.Resize(submeshIndex + 1); + if (submeshes.Items[submeshIndex] == null) + submeshes.Items[submeshIndex] = new ExposedList(); + } + var submesh = submeshes.Items[submeshIndex]; + submesh.Clear(false); + + var skeleton = instruction.skeleton; + var drawOrderItems = skeleton.drawOrder.Items; + + Color32 color; + float skeletonA = skeleton.a * 255, skeletonR = skeleton.r, skeletonG = skeleton.g, skeletonB = skeleton.b; + Vector2 meshBoundsMin = this.meshBoundsMin, meshBoundsMax = this.meshBoundsMax; + + // Settings + float zSpacing = settings.zSpacing; + + bool pmaVertexColors = settings.pmaVertexColors; + for (int slotIndex = instruction.startSlot; slotIndex < instruction.endSlot; slotIndex++) { + var slot = drawOrderItems[slotIndex]; + var attachment = slot.attachment; + float z = zSpacing * slotIndex; + + var workingVerts = this.tempVerts; + float[] uvs; + + Color c = default(Color); + + var region = attachment as RegionAttachment; + if (region != null) { + region.ComputeWorldVertices(slot.bone, workingVerts, 0); + uvs = region.uvs; + c.r = region.r; c.g = region.g; c.b = region.b; c.a = region.a; + } else { + continue; + } + + if (pmaVertexColors) { + color.a = (byte)(skeletonA * slot.a * c.a); + color.r = (byte)(skeletonR * slot.r * c.r * color.a); + color.g = (byte)(skeletonG * slot.g * c.g * color.a); + color.b = (byte)(skeletonB * slot.b * c.b * color.a); + if (slot.data.blendMode == BlendMode.Additive) color.a = 0; + } else { + color.a = (byte)(skeletonA * slot.a * c.a); + color.r = (byte)(skeletonR * slot.r * c.r * 255); + color.g = (byte)(skeletonG * slot.g * c.g * 255); + color.b = (byte)(skeletonB * slot.b * c.b * 255); + } + + { + if (settings.tintBlack) + AddAttachmentTintBlack(slot.r2, slot.g2, slot.b2, attachmentVertexCount); + + //AddAttachment(workingVerts, uvs, color, attachmentTriangleIndices, attachmentVertexCount, attachmentIndexCount, ref meshBoundsMin, ref meshBoundsMax, z); + int ovc = vertexBuffer.Count; + // Add data to vertex buffers + { + int newVertexCount = ovc + attachmentVertexCount; + if (newVertexCount > vertexBuffer.Items.Length) { // Manual ExposedList.Resize() + Array.Resize(ref vertexBuffer.Items, newVertexCount); + Array.Resize(ref uvBuffer.Items, newVertexCount); + Array.Resize(ref colorBuffer.Items, newVertexCount); + } + vertexBuffer.Count = uvBuffer.Count = colorBuffer.Count = newVertexCount; + } + + var vbi = vertexBuffer.Items; + var ubi = uvBuffer.Items; + var cbi = colorBuffer.Items; + for (int i = 0; i < attachmentVertexCount; i++) { + int vi = ovc + i; + int i2 = i << 1; // i * 2 + float x = workingVerts[i2]; + float y = workingVerts[i2 + 1]; + + vbi[vi].x = x; + vbi[vi].y = y; + vbi[vi].z = z; + ubi[vi].x = uvs[i2]; + ubi[vi].y = uvs[i2 + 1]; + cbi[vi] = color; + + // Calculate bounds. + if (x < meshBoundsMin.x) meshBoundsMin.x = x; + else if (x > meshBoundsMax.x) meshBoundsMax.x = x; + if (y < meshBoundsMin.y) meshBoundsMin.y = y; + else if (y > meshBoundsMax.y) meshBoundsMax.y = y; + } + + // TODO: Simplify triangle buffer handling. + // Add data to triangle buffer + int oldTriangleCount = submesh.Count; + { //submesh.Resize(oldTriangleCount + attachmentIndexCount); + int newTriangleCount = oldTriangleCount + attachmentIndexCount; + if (newTriangleCount > submesh.Items.Length) Array.Resize(ref submesh.Items, newTriangleCount); + submesh.Count = newTriangleCount; + } + var submeshItems = submesh.Items; + for (int i = 0; i < attachmentIndexCount; i++) + submeshItems[oldTriangleCount + i] = attachmentTriangleIndices[i] + ovc; + } + } + + this.meshBoundsMin = meshBoundsMin; + this.meshBoundsMax = meshBoundsMax; + meshBoundsThickness = instruction.endSlot * zSpacing; + + // Trim or zero submesh triangles. + var currentSubmeshItems = submesh.Items; + for (int i = submesh.Count, n = currentSubmeshItems.Length; i < n; i++) + currentSubmeshItems[i] = 0; + + // Next AddSubmesh will use a new submeshIndex value. + submeshIndex++; + } + + void AddAttachmentTintBlack (float r2, float g2, float b2, int vertexCount) { + var rg = new Vector2(r2, g2); + var bo = new Vector2(b2, 1f); + + int ovc = vertexBuffer.Count; + int newVertexCount = ovc + vertexCount; + { + if (uv2 == null) { + uv2 = new ExposedList(); + 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; + var sbi = submeshes.Items; + int submeshCount = submeshes.Count; + + // Zero the extra. + { + int listCount = vertexBuffer.Count; + int arrayLength = vertexBuffer.Items.Length; + var vector3zero = Vector3.zero; + for (int i = listCount; i < arrayLength; i++) + vbi[i] = vector3zero; + } + + // Set the vertex buffer. + { + mesh.vertices = vbi; + mesh.uv = ubi; + mesh.colors32 = cbi; + + if (meshBoundsMin.x == BoundsMinDefault) { + mesh.bounds = new Bounds(); + } else { + Vector2 halfSize = (meshBoundsMax - meshBoundsMin) * 0.5f; + mesh.bounds = new Bounds { + center = (Vector3)(meshBoundsMin + halfSize), + extents = new Vector3(halfSize.x, halfSize.y, meshBoundsThickness * 0.5f) + }; + //mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax); + } + } + + { + int vertexCount = this.vertexBuffer.Count; + if (settings.addNormals) { + int oldLength = 0; + + if (normals == null) + normals = new Vector3[vertexCount]; + else + oldLength = normals.Length; + + if (oldLength < vertexCount) { + Array.Resize(ref this.normals, vertexCount); + var localNormals = this.normals; + for (int i = oldLength; i < vertexCount; i++) localNormals[i] = Vector3.back; + } + mesh.normals = this.normals; + } + + if (settings.tintBlack) { + mesh.uv2 = this.uv2.Items; + mesh.uv3 = this.uv3.Items; + } + + if (settings.calculateTangents) { + MeshGenerator.SolveTangents2DEnsureSize(ref this.tangents, ref this.tempTanBuffer, vertexCount); + for (int i = 0; i < submeshCount; i++) { + var submesh = sbi[i].Items; + int triangleCount = sbi[i].Count; + MeshGenerator.SolveTangents2DTriangles(this.tempTanBuffer, submesh, triangleCount, vbi, ubi, vertexCount); + } + MeshGenerator.SolveTangents2DBuffer(this.tangents, this.tempTanBuffer, vertexCount); + mesh.tangents = this.tangents; + } + } + } + + public void FillTriangles (Mesh mesh) { + int submeshCount = submeshes.Count; + var submeshesItems = submeshes.Items; + mesh.subMeshCount = submeshCount; + + for (int i = 0; i < submeshCount; i++) + mesh.SetTriangles(submeshesItems[i].Items, i, false); + } + + public void FillTrianglesSingle (Mesh mesh) { + mesh.SetTriangles(submeshes.Items[0].Items, 0, false); + } + #endregion + + public void TrimExcess () { + vertexBuffer.TrimExcess(); + uvBuffer.TrimExcess(); + colorBuffer.TrimExcess(); + + if (uv2 != null) uv2.TrimExcess(); + if (uv3 != null) uv3.TrimExcess(); + + int count = vertexBuffer.Count; + if (normals != null) Array.Resize(ref normals, count); + if (tangents != null) Array.Resize(ref tangents, count); + } + + #region TangentSolver2D + // Thanks to contributions from forum user ToddRivers + + /// 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) { + if (tangentBuffer == null || tangentBuffer.Length < vertexCount) + tangentBuffer = new Vector4[vertexCount]; + + if (tempTanBuffer == null || tempTanBuffer.Length < vertexCount * 2) + tempTanBuffer = new Vector2[vertexCount * 2]; // two arrays in one. + } + + /// 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 + } + + public class MeshRendererBuffers : IDisposable { + DoubleBuffered doubleBufferedMesh; + internal readonly ExposedList submeshMaterials = new ExposedList(); + internal Material[] sharedMaterials = new Material[0]; + + public void Initialize () { + doubleBufferedMesh = new DoubleBuffered(); + } + + public Material[] GetUpdatedShaderdMaterialsArray () { + if (submeshMaterials.Count == sharedMaterials.Length) + submeshMaterials.CopyTo(sharedMaterials); + else + sharedMaterials = submeshMaterials.ToArray(); + + return sharedMaterials; + } + + public bool MaterialsChangedInLastUpdate () { + int newSubmeshMaterials = submeshMaterials.Count; + var sharedMaterials = this.sharedMaterials; + if (newSubmeshMaterials != sharedMaterials.Length) return true; + + var submeshMaterialsItems = submeshMaterials.Items; + for (int i = 0; i < newSubmeshMaterials; i++) + if (!Material.ReferenceEquals(submeshMaterialsItems[i], sharedMaterials[i])) return true; //if (submeshMaterialsItems[i].GetInstanceID() != sharedMaterials[i].GetInstanceID()) return true; + + return false; + } + + public void UpdateSharedMaterials (ExposedList 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.NewMesh(); + public SkeletonRendererInstruction instructionUsed = new SkeletonRendererInstruction(); + + public void Dispose () { + if (mesh != null) { + #if UNITY_EDITOR + if (Application.isEditor && !Application.isPlaying) + UnityEngine.Object.DestroyImmediate(mesh); + else + UnityEngine.Object.Destroy(mesh); + #else + UnityEngine.Object.Destroy(mesh); + #endif + } + mesh = null; + } + } + } + + public class SkeletonRendererInstruction { + public bool immutableTriangles; + public readonly ExposedList submeshInstructions = new ExposedList(); + + #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 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 drawOrder = instructionsItems[0].skeleton.drawOrder.Items; + for (int i = 0; i < attachmentCount; i++) + attachmentsItems[i] = drawOrder[startSlot + i].attachment; + #endif + } + + public void Set (SkeletonRendererInstruction other) { + this.immutableTriangles = other.immutableTriangles; + + #if SPINE_TRIANGLECHECK + this.hasActiveClipping = other.hasActiveClipping; + this.rawVertexCount = other.rawVertexCount; + this.attachments.Clear(false); + this.attachments.GrowIfNeeded(other.attachments.Capacity); + this.attachments.Count = other.attachments.Count; + other.attachments.CopyTo(this.attachments.Items); + #endif + + this.submeshInstructions.Clear(false); + this.submeshInstructions.GrowIfNeeded(other.submeshInstructions.Capacity); + this.submeshInstructions.Count = other.submeshInstructions.Count; + other.submeshInstructions.CopyTo(this.submeshInstructions.Items); + } + + public static bool GeometryNotEqual (SkeletonRendererInstruction a, SkeletonRendererInstruction b) { + #if SPINE_TRIANGLECHECK + #if UNITY_EDITOR + if (!Application.isPlaying) + return true; + #endif + + if (a.hasActiveClipping || b.hasActiveClipping) return true; // Triangles are unpredictable when clipping is active. + + // Everything below assumes the raw vertex and triangle counts were used. (ie, no clipping was done) + if (a.rawVertexCount != b.rawVertexCount) return true; + + if (a.immutableTriangles != b.immutableTriangles) return true; + + int attachmentCountB = b.attachments.Count; + if (a.attachments.Count != attachmentCountB) return true; // Bounds check for the looped storedAttachments count below. + + // Submesh count changed + int submeshCountA = a.submeshInstructions.Count; + int submeshCountB = b.submeshInstructions.Count; + if (submeshCountA != submeshCountB) return true; + + // Submesh Instruction mismatch + var submeshInstructionsItemsA = a.submeshInstructions.Items; + var submeshInstructionsItemsB = b.submeshInstructions.Items; + + var attachmentsA = a.attachments.Items; + var attachmentsB = b.attachments.Items; + for (int i = 0; i < attachmentCountB; i++) + if (!System.Object.ReferenceEquals(attachmentsA[i], attachmentsB[i])) return true; + + for (int i = 0; i < submeshCountB; i++) { + var submeshA = submeshInstructionsItemsA[i]; + var submeshB = submeshInstructionsItemsB[i]; + + if (!( + submeshA.rawVertexCount == submeshB.rawVertexCount && + submeshA.startSlot == submeshB.startSlot && + submeshA.endSlot == submeshB.endSlot + && submeshA.rawTriangleCount == submeshB.rawTriangleCount && + submeshA.rawFirstVertexIndex == submeshB.rawFirstVertexIndex + )) + return true; + } + + return false; + #else + // In normal immutable triangle use, immutableTriangles will be initially false, forcing the smartmesh to update the first time but never again after that, unless there was an immutableTriangles flag mismatch.. + if (a.immutableTriangles || b.immutableTriangles) + return (a.immutableTriangles != b.immutableTriangles); + + return true; + #endif + } + } + } diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs index a24bc4aa1..2bd6d1603 100644 --- a/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs @@ -41,6 +41,7 @@ namespace Spine.Unity.Editor { SerializedProperty material_, color_; SerializedProperty skeletonDataAsset_, initialSkinName_; SerializedProperty startingAnimation_, startingLoop_, timeScale_, freeze_, unscaledTime_, tintBlack_; + SerializedProperty meshGeneratorSettings_; SerializedProperty raycastTarget_; SkeletonGraphic thisSkeletonGraphic; @@ -65,6 +66,8 @@ namespace Spine.Unity.Editor { timeScale_ = so.FindProperty("timeScale"); unscaledTime_ = so.FindProperty("unscaledTime"); freeze_ = so.FindProperty("freeze"); + + meshGeneratorSettings_ = so.FindProperty("meshGenerator").FindPropertyRelative("settings"); } public override void OnInspectorGUI () { @@ -80,10 +83,12 @@ namespace Spine.Unity.Editor { serializedObject.Update(); return; } + using (new SpineInspectorUtility.BoxScope()) { + EditorGUILayout.PropertyField(meshGeneratorSettings_, new GUIContent("Advanced..."), includeChildren: true); + } EditorGUILayout.Space(); EditorGUILayout.PropertyField(initialSkinName_); - //EditorGUILayout.PropertyField(tintBlack_); EditorGUILayout.Space(); EditorGUILayout.LabelField("Animation", EditorStyles.boldLabel); EditorGUILayout.PropertyField(startingAnimation_); @@ -106,7 +111,7 @@ namespace Spine.Unity.Editor { [MenuItem("CONTEXT/SkeletonGraphic/Match RectTransform with Mesh Bounds")] static void MatchRectTransformWithBounds (MenuCommand command) { var skeletonGraphic = (SkeletonGraphic)command.context; - var mesh = skeletonGraphic.SpineMeshGenerator.LastGeneratedMesh; + var mesh = skeletonGraphic.GetComponent().sharedMesh; mesh.RecalculateBounds(); var bounds = mesh.bounds; diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs index 1e2854e01..a86f9f013 100644 --- a/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/SkeletonGraphic.cs @@ -150,9 +150,10 @@ namespace Spine.Unity { protected Spine.AnimationState state; public Spine.AnimationState AnimationState { get { return state; } } - // This is any object that can give you a mesh when you give it a skeleton to render. - protected Spine.Unity.MeshGeneration.ISimpleMeshGenerator spineMeshGenerator; - public Spine.Unity.MeshGeneration.ISimpleMeshGenerator SpineMeshGenerator { get { return this.spineMeshGenerator; } } + [SerializeField] protected Spine.Unity.MeshGenerator meshGenerator = new MeshGenerator(); + public Spine.Unity.MeshGenerator MeshGenerator { get { return this.meshGenerator; } } + DoubleBuffered meshBuffers; + SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction(); public event UpdateBonesDelegate UpdateLocal; public event UpdateBonesDelegate UpdateWorld; @@ -180,9 +181,7 @@ namespace Spine.Unity { } this.skeleton = new Skeleton(skeletonData); - this.spineMeshGenerator = new Spine.Unity.MeshGeneration.ArraysSimpleMeshGenerator(); // You can switch this out with any other implementer of Spine.Unity.MeshGeneration.ISimpleMeshGenerator - //this.spineMeshGenerator.AddBlackTint = this.tintBlack; - this.spineMeshGenerator.PremultiplyVertexColors = true; + meshBuffers = new DoubleBuffered(); // Set the initial Skin and Animation if (!string.IsNullOrEmpty(initialSkinName)) @@ -193,14 +192,28 @@ namespace Spine.Unity { } public void UpdateMesh () { - if (this.IsValid) { - skeleton.SetColor(this.color); - if (canvas != null) - spineMeshGenerator.Scale = canvas.referencePixelsPerUnit; //JOHN: left a todo: move this to a listener to of the canvas? + if (!this.IsValid) return; - canvasRenderer.SetMesh(spineMeshGenerator.GenerateMesh(skeleton)); - //this.UpdateMaterial(); // TODO: This allocates memory. - } + skeleton.SetColor(this.color); + + var currentInstructions = this.currentInstructions; + MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, true, this.material); + meshGenerator.BeginNewMesh(); + meshGenerator.AddSubmesh(currentInstructions.submeshInstructions.Items[0]); + if (canvas != null) + meshGenerator.ScaleVertexData(canvas.referencePixelsPerUnit); + + var smartMesh = meshBuffers.GetNext(); + var mesh = smartMesh.mesh; + + meshGenerator.FillVertexData(mesh); + if (SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed)) + meshGenerator.FillTrianglesSingle(mesh); + + canvasRenderer.SetMesh(mesh); + smartMesh.instructionUsed.Set(currentInstructions); + + //this.UpdateMaterial(); // TODO: This allocates memory. } #endregion } diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs index b8fbb5952..a5326bab9 100644 --- a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonPartsRenderer.cs @@ -29,15 +29,14 @@ *****************************************************************************/ using UnityEngine; -using Spine.Unity.MeshGeneration; namespace Spine.Unity.Modules { [RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))] public class SkeletonPartsRenderer : MonoBehaviour { #region Properties - ISubmeshSetMeshGenerator meshGenerator; - public ISubmeshSetMeshGenerator MeshGenerator { + MeshGenerator meshGenerator; + public MeshGenerator MeshGenerator { get { LazyIntialize(); return meshGenerator; @@ -61,11 +60,21 @@ namespace Spine.Unity.Modules { } #endregion + MeshRendererBuffers buffers; + SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction(); + + void LazyIntialize () { - if (meshGenerator != null) return; - meshGenerator = new ArraysSubmeshSetMeshGenerator(); - meshFilter = GetComponent(); - meshRenderer = GetComponent(); + if (buffers == null) { + buffers = new MeshRendererBuffers(); + buffers.Initialize(); + + if (meshGenerator != null) return; + meshGenerator = new MeshGenerator(); + meshFilter = GetComponent(); + meshRenderer = GetComponent(); + currentInstructions.Clear(); + } } public void ClearMesh () { @@ -75,9 +84,31 @@ namespace Spine.Unity.Modules { public void RenderParts (ExposedList instructions, int startSubmesh, int endSubmesh) { LazyIntialize(); - MeshAndMaterials m = meshGenerator.GenerateMesh(instructions, startSubmesh, endSubmesh); - meshFilter.sharedMesh = m.mesh; - meshRenderer.sharedMaterials = m.materials; + + // STEP 1: Create instruction + currentInstructions.SetWithSubset(instructions, startSubmesh, endSubmesh); + + // STEP 2: Generate mesh buffers. + var currentInstructionsSubmeshesItems = currentInstructions.submeshInstructions.Items; + meshGenerator.BeginNewMesh(); + for (int i = 0; i < currentInstructions.submeshInstructions.Count; i++) + meshGenerator.AddSubmesh(currentInstructionsSubmeshesItems[i]); + + buffers.UpdateSharedMaterials(currentInstructions.submeshInstructions); + + // STEP 3: modify mesh. + var smartMesh = buffers.GetNextMesh(); + var mesh = smartMesh.mesh; + meshGenerator.FillVertexData(mesh); + if (SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed)) { + meshGenerator.FillTriangles(mesh); + meshRenderer.sharedMaterials = buffers.GetUpdatedShaderdMaterialsArray(); + } else if (buffers.MaterialsChangedInLastUpdate()) { + meshRenderer.sharedMaterials = buffers.GetUpdatedShaderdMaterialsArray(); + } + + meshFilter.sharedMesh = mesh; + smartMesh.instructionUsed.Set(currentInstructions); } public void SetPropertyBlock (MaterialPropertyBlock block) { diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs index 061ffeb6e..6f4a87acd 100644 --- a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs @@ -130,40 +130,36 @@ namespace Spine.Unity.Modules { MaterialPropertyBlock copiedBlock; - void HandleRender (SkeletonRenderer.SmartMesh.Instruction instruction) { + void HandleRender (SkeletonRendererInstruction instruction) { int rendererCount = partsRenderers.Count; if (rendererCount <= 0) return; if (copyPropertyBlock) mainMeshRenderer.GetPropertyBlock(copiedBlock); + var settings = new MeshGenerator.Settings { + addNormals = skeletonRenderer.addNormals, + calculateTangents = skeletonRenderer.calculateTangents, + immutableTriangles = false, // parts cannot do immutable triangles. + pmaVertexColors = skeletonRenderer.pmaVertexColors, + renderMeshes = skeletonRenderer.renderMeshes, + tintBlack = skeletonRenderer.tintBlack, + useClipping = true, + zSpacing = skeletonRenderer.zSpacing + }; + var submeshInstructions = instruction.submeshInstructions; var submeshInstructionsItems = submeshInstructions.Items; int lastSubmeshInstruction = submeshInstructions.Count - 1; - #if SPINE_OPTIONAL_NORMALS - bool addNormals = skeletonRenderer.calculateNormals; - #endif - - #if SPINE_OPTIONAL_SOLVETANGENTS - bool addTangents = skeletonRenderer.calculateTangents; - #endif - - bool pmaVertexColors = skeletonRenderer.pmaVertexColors; - int rendererIndex = 0; var currentRenderer = partsRenderers[rendererIndex]; for (int si = 0, start = 0; si <= lastSubmeshInstruction; si++) { if (submeshInstructionsItems[si].forceSeparate || si == lastSubmeshInstruction) { // Apply properties var meshGenerator = currentRenderer.MeshGenerator; - #if SPINE_OPTIONAL_NORMALS - meshGenerator.AddNormals = addNormals; - #endif - #if SPINE_OPTIONAL_SOLVETANGENTS - meshGenerator.AddTangents = addTangents; - #endif - meshGenerator.PremultiplyVertexColors = pmaVertexColors; + meshGenerator.settings = settings; + if (copyPropertyBlock) currentRenderer.SetPropertyBlock(copiedBlock); diff --git a/spine-unity/Assets/spine-unity/SkeletonRenderer.cs b/spine-unity/Assets/spine-unity/SkeletonRenderer.cs index 80e0de1c8..ad6dccd61 100644 --- a/spine-unity/Assets/spine-unity/SkeletonRenderer.cs +++ b/spine-unity/Assets/spine-unity/SkeletonRenderer.cs @@ -30,14 +30,9 @@ #define SPINE_OPTIONAL_RENDEROVERRIDE #define SPINE_OPTIONAL_MATERIALOVERRIDE -#define SPINE_OPTIONAL_NORMALS -#define SPINE_OPTIONAL_SOLVETANGENTS -//#define SPINE_OPTIONAL_FRONTFACING -using System; using System.Collections.Generic; using UnityEngine; -using Spine.Unity.MeshGeneration; namespace Spine.Unity { /// Renders a skeleton. @@ -54,30 +49,25 @@ namespace Spine.Unity { #region Advanced // Submesh Separation - [UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators")] - [SpineSlot] - public string[] separatorSlotNames = new string[0]; - [System.NonSerialized] - public readonly List separatorSlots = new List(); + [UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators")] [SpineSlot] public string[] separatorSlotNames = new string[0]; + [System.NonSerialized] public readonly List separatorSlots = new List(); - public float zSpacing; - public bool renderMeshes = true, immutableTriangles; + [Range(-0.1f, 0f)] public float zSpacing; + public bool renderMeshes = true; + public bool immutableTriangles = false; public bool pmaVertexColors = true; public bool clearStateOnDisable = false; public bool tintBlack = false; - #if SPINE_OPTIONAL_NORMALS - public bool calculateNormals; - #endif - #if SPINE_OPTIONAL_SOLVETANGENTS + [UnityEngine.Serialization.FormerlySerializedAs("calculateNormals")] + public bool addNormals; public bool calculateTangents; - #endif public bool logErrors = false; #if SPINE_OPTIONAL_RENDEROVERRIDE public bool disableRenderingOnOverride = true; - public delegate void InstructionDelegate (SkeletonRenderer.SmartMesh.Instruction instruction); + public delegate void InstructionDelegate (SkeletonRendererInstruction instruction); event InstructionDelegate generateMeshOverride; public event InstructionDelegate GenerateMeshOverride { add { @@ -118,27 +108,10 @@ namespace Spine.Unity { return skeleton; } } - - Spine.Unity.DoubleBuffered doubleBufferedMesh; - readonly SmartMesh.Instruction currentInstructions = new SmartMesh.Instruction(); - readonly ExposedList submeshes = new ExposedList(); - readonly ExposedList submeshMaterials = new ExposedList(); - Material[] sharedMaterials = new Material[0]; - float[] tempVertices = new float[8]; - Vector3[] vertices; - Color32[] colors; - Vector2[] uvs; - - Vector2[] uv2; - Vector2[] uv3; - - #if SPINE_OPTIONAL_NORMALS - Vector3[] normals; - #endif - #if SPINE_OPTIONAL_SOLVETANGENTS - Vector4[] tangents; - Vector2[] tempTanBuffer; - #endif + + [System.NonSerialized] readonly SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction(); + [System.NonSerialized] readonly MeshGenerator meshGenerator = new MeshGenerator(); + [System.NonSerialized] readonly MeshRendererBuffers rendererBuffers = new MeshRendererBuffers(); #region Runtime Instantiation public static T NewSpineGameObject (SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer { @@ -167,10 +140,7 @@ namespace Spine.Unity { } void OnDestroy () { - if (doubleBufferedMesh == null) return; - doubleBufferedMesh.GetNext().Dispose(); - doubleBufferedMesh.GetNext().Dispose(); - doubleBufferedMesh = null; + rendererBuffers.Dispose(); } protected virtual void ClearState () { @@ -192,14 +162,9 @@ namespace Spine.Unity { if (meshRenderer != null) meshRenderer.sharedMaterial = null; currentInstructions.Clear(); - vertices = null; - colors = null; - uvs = null; - sharedMaterials = new Material[0]; - submeshMaterials.Clear(); - submeshes.Clear(); + rendererBuffers.Clear(); + meshGenerator.BeginNewMesh(); skeleton = null; - valid = false; } @@ -216,8 +181,7 @@ namespace Spine.Unity { meshFilter = GetComponent(); meshRenderer = GetComponent(); - doubleBufferedMesh = new DoubleBuffered(); - vertices = new Vector3[0]; + rendererBuffers.Initialize(); skeleton = new Skeleton(skeletonData); if (!string.IsNullOrEmpty(initialSkinName) && initialSkinName != "default") @@ -234,446 +198,72 @@ namespace Spine.Unity { } public virtual void LateUpdate () { - if (!valid) - return; + if (!valid) return; - if ( - (!meshRenderer.enabled) - #if SPINE_OPTIONAL_RENDEROVERRIDE - && this.generateMeshOverride == null - #endif - ) - return; - - - // STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. ============================================================ - ExposedList drawOrder = skeleton.drawOrder; - var drawOrderItems = drawOrder.Items; - int drawOrderCount = drawOrder.Count; - bool renderMeshes = this.renderMeshes; - - // Clear last state of attachments and submeshes - var workingInstruction = this.currentInstructions; - var workingAttachments = workingInstruction.attachments; - workingAttachments.Clear(false); - workingAttachments.GrowIfNeeded(drawOrderCount); - workingAttachments.Count = drawOrderCount; - var workingAttachmentsItems = workingInstruction.attachments.Items; - - var workingSubmeshInstructions = workingInstruction.submeshInstructions; // Items array should not be cached. There is dynamic writing to this list. - workingSubmeshInstructions.Clear(false); - - #if !SPINE_TK2D - bool isCustomSlotMaterialsPopulated = customSlotMaterials.Count > 0; + #if SPINE_OPTIONAL_RENDEROVERRIDE + bool doMeshOverride = generateMeshOverride != null; + if ((!meshRenderer.enabled) && !doMeshOverride) return; + #else + const bool doMeshOverride = false; + if (!meshRenderer.enabled) return; #endif - bool hasSeparators = separatorSlots.Count > 0; - int vertexCount = 0; - int submeshVertexCount = 0; - int submeshTriangleCount = 0, submeshFirstVertex = 0, submeshStartSlotIndex = 0; - Material lastMaterial = null; - for (int i = 0; i < drawOrderCount; i++) { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.attachment; - workingAttachmentsItems[i] = attachment; + var currentInstructions = this.currentInstructions; - object rendererObject = null; // An AtlasRegion in plain Spine-Unity. Spine-TK2D hooks into TK2D's system. eventual source of Material object. - int attachmentVertexCount, attachmentTriangleCount; - bool noRender = false; - - var regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - rendererObject = regionAttachment.RendererObject; - attachmentVertexCount = 4; - attachmentTriangleCount = 6; - } else { - if (!renderMeshes) { - noRender = true; - attachmentVertexCount = 0; - attachmentTriangleCount = 0; - //continue; - } else { - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - rendererObject = meshAttachment.RendererObject; - attachmentVertexCount = meshAttachment.worldVerticesLength >> 1; - attachmentTriangleCount = meshAttachment.triangles.Length; - } else { - noRender = true; - attachmentVertexCount = 0; - attachmentTriangleCount = 0; - //continue; - } - } - } - - // Create a new SubmeshInstruction when material changes. (or when forced to separate by a submeshSeparator) - // Slot with a separator/new material will become the starting slot of the next new instruction. - bool forceSeparate = (hasSeparators && separatorSlots.Contains(slot)); - if (noRender) { - if (forceSeparate && vertexCount > 0 - #if SPINE_OPTIONAL_RENDEROVERRIDE - && this.generateMeshOverride != null - #endif - ) { - workingSubmeshInstructions.Add( - new Spine.Unity.MeshGeneration.SubmeshInstruction { - skeleton = this.skeleton, - material = lastMaterial, - startSlot = submeshStartSlotIndex, - endSlot = i, - triangleCount = submeshTriangleCount, - firstVertexIndex = submeshFirstVertex, - vertexCount = submeshVertexCount, - forceSeparate = forceSeparate - } - ); - submeshTriangleCount = 0; - submeshVertexCount = 0; - submeshFirstVertex = vertexCount; - submeshStartSlotIndex = i; - } - } else { - #if !SPINE_TK2D - Material material; - if (isCustomSlotMaterialsPopulated) { - if (!customSlotMaterials.TryGetValue(slot, out material)) - material = (Material)((AtlasRegion)rendererObject).page.rendererObject; - } else { - material = (Material)((AtlasRegion)rendererObject).page.rendererObject; - } - #else - Material material = (rendererObject.GetType() == typeof(Material)) ? (Material)rendererObject : (Material)((AtlasRegion)rendererObject).page.rendererObject; - #endif - - if (vertexCount > 0 && (forceSeparate || lastMaterial.GetInstanceID() != material.GetInstanceID())) { - workingSubmeshInstructions.Add( - new Spine.Unity.MeshGeneration.SubmeshInstruction { - skeleton = this.skeleton, - material = lastMaterial, - startSlot = submeshStartSlotIndex, - endSlot = i, - triangleCount = submeshTriangleCount, - firstVertexIndex = submeshFirstVertex, - vertexCount = submeshVertexCount, - forceSeparate = forceSeparate - } - ); - submeshTriangleCount = 0; - submeshVertexCount = 0; - submeshFirstVertex = vertexCount; - submeshStartSlotIndex = i; - } - // Update state for the next iteration. - lastMaterial = material; - submeshTriangleCount += attachmentTriangleCount; - vertexCount += attachmentVertexCount; - submeshVertexCount += attachmentVertexCount; - } - } - - if (submeshVertexCount != 0) { - workingSubmeshInstructions.Add( - new Spine.Unity.MeshGeneration.SubmeshInstruction { - skeleton = this.skeleton, - material = lastMaterial, - startSlot = submeshStartSlotIndex, - endSlot = drawOrderCount, - triangleCount = submeshTriangleCount, - firstVertexIndex = submeshFirstVertex, - vertexCount = submeshVertexCount, - forceSeparate = false - } - ); - } - - workingInstruction.vertexCount = vertexCount; - workingInstruction.immutableTriangles = this.immutableTriangles; + // STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. ============================================================ + MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, customSlotMaterials, separatorSlots, doMeshOverride, this.immutableTriangles, this.renderMeshes); // STEP 1.9. Post-process workingInstructions. ============================================================ - #if SPINE_OPTIONAL_MATERIALOVERRIDE - // Material overrides are done here so they can be applied per submesh instead of per slot - // but they will still be passed through the GenerateMeshOverride delegate, - // and will still go through the normal material match check step in STEP 3. - if (customMaterialOverride.Count > 0) { // isCustomMaterialOverridePopulated - var workingSubmeshInstructionsItems = workingSubmeshInstructions.Items; - for (int i = 0; i < workingSubmeshInstructions.Count; i++) { - var m = workingSubmeshInstructionsItems[i].material; - Material mo; - if (customMaterialOverride.TryGetValue(m, out mo)) { - workingSubmeshInstructionsItems[i].material = mo; - } - } - } + if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated + MeshGenerator.TryReplaceMaterials(currentInstructions.submeshInstructions, customMaterialOverride); #endif + #if SPINE_OPTIONAL_RENDEROVERRIDE - if (this.generateMeshOverride != null) { - this.generateMeshOverride(workingInstruction); + if (doMeshOverride) { + this.generateMeshOverride(currentInstructions); if (disableRenderingOnOverride) return; } #endif // STEP 2. Update vertex buffer based on verts from the attachments. ============================================================ - // Uses values that were also stored in workingInstruction. - if (tintBlack) { - ArraysMeshGenerator.EnsureSize(vertexCount, ref this.uv2); - ArraysMeshGenerator.EnsureSize(vertexCount, ref this.uv3); + meshGenerator.BeginNewMesh(); + meshGenerator.settings = new MeshGenerator.Settings { + pmaVertexColors = this.pmaVertexColors, + zSpacing = this.zSpacing, + useClipping = true, + tintBlack = this.tintBlack, + calculateTangents = this.calculateTangents, + renderMeshes = this.renderMeshes, + addNormals = this.addNormals + }; + + var workingSubmeshInstructions = currentInstructions.submeshInstructions; + foreach (var submeshInstruction in workingSubmeshInstructions) { + meshGenerator.AddSubmesh(submeshInstruction); } - - #if SPINE_OPTIONAL_NORMALS - bool vertexCountIncreased = ArraysMeshGenerator.EnsureSize(vertexCount, ref this.vertices, ref this.uvs, ref this.colors); - if (vertexCountIncreased && calculateNormals) { - Vector3[] localNormals = this.normals = new Vector3[vertexCount]; - Vector3 normal = new Vector3(0, 0, -1); - for (int i = 0; i < vertexCount; i++) - localNormals[i] = normal; - } - #else - ArraysMeshGenerator.EnsureSize(vertexCount, ref this.vertices, ref this.uvs, ref this.colors); - #endif - - Vector3 meshBoundsMin; - Vector3 meshBoundsMax; - if (vertexCount <= 0) { - meshBoundsMin = new Vector3(0, 0, 0); - meshBoundsMax = new Vector3(0, 0, 0); - } else { - meshBoundsMin.x = int.MaxValue; - meshBoundsMin.y = int.MaxValue; - meshBoundsMax.x = int.MinValue; - meshBoundsMax.y = int.MinValue; - - if (zSpacing > 0f) { - meshBoundsMin.z = 0f; - meshBoundsMax.z = zSpacing * (drawOrderCount - 1); - } else { - meshBoundsMin.z = zSpacing * (drawOrderCount - 1); - meshBoundsMax.z = 0f; - } - } - int vertexIndex = 0; - - if (tintBlack) - ArraysMeshGenerator.FillBlackUVs(skeleton, 0, drawOrderCount, this.uv2, this.uv3, vertexIndex, renderMeshes); // This needs to be called before FillVerts so we have the correct vertexIndex argument. - - ArraysMeshGenerator.FillVerts(skeleton, 0, drawOrderCount, this.zSpacing, pmaVertexColors, this.vertices, this.uvs, this.colors, ref vertexIndex, ref tempVertices, ref meshBoundsMin, ref meshBoundsMax, renderMeshes); - - + // Step 3. Move the mesh data into a UnityEngine.Mesh ============================================================ - var currentSmartMesh = doubleBufferedMesh.GetNext(); // Double-buffer for performance. + var currentSmartMesh = rendererBuffers.GetNextMesh(); // Double-buffer for performance. var currentMesh = currentSmartMesh.mesh; - currentMesh.vertices = this.vertices; - currentMesh.colors32 = colors; - currentMesh.uv = uvs; - currentMesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax); + meshGenerator.FillVertexData(currentMesh); - if (tintBlack) { - currentMesh.uv2 = this.uv2; - currentMesh.uv3 = this.uv3; - } - - var currentSmartMeshInstructionUsed = currentSmartMesh.instructionUsed; - #if SPINE_OPTIONAL_NORMALS - if (calculateNormals && currentSmartMeshInstructionUsed.vertexCount < vertexCount) - currentMesh.normals = normals; - #endif + rendererBuffers.UpdateSharedMaterials(workingSubmeshInstructions); // Check if the triangles should also be updated. - // This thorough structure check is cheaper than updating triangles every frame. - bool mustUpdateMeshStructure = CheckIfMustUpdateMeshStructure(workingInstruction, currentSmartMeshInstructionUsed); - int submeshCount = workingSubmeshInstructions.Count; - if (mustUpdateMeshStructure) { - var thisSubmeshMaterials = this.submeshMaterials; - thisSubmeshMaterials.Clear(false); - - int oldSubmeshCount = submeshes.Count; - - if (submeshes.Capacity < submeshCount) - submeshes.Capacity = submeshCount; - for (int i = oldSubmeshCount; i < submeshCount; i++) - submeshes.Items[i] = new ArraysMeshGenerator.SubmeshTriangleBuffer(workingSubmeshInstructions.Items[i].triangleCount); - submeshes.Count = submeshCount; - - var mutableTriangles = !workingInstruction.immutableTriangles; - for (int i = 0, last = submeshCount - 1; i < submeshCount; i++) { - var submeshInstruction = workingSubmeshInstructions.Items[i]; - - if (mutableTriangles || i >= oldSubmeshCount) { - - var currentSubmesh = submeshes.Items[i]; - int instructionTriangleCount = submeshInstruction.triangleCount; - if (renderMeshes) { - ArraysMeshGenerator.FillTriangles(ref currentSubmesh.triangles, skeleton, instructionTriangleCount, submeshInstruction.firstVertexIndex, submeshInstruction.startSlot, submeshInstruction.endSlot, (i == last)); - currentSubmesh.triangleCount = instructionTriangleCount; - } else { - ArraysMeshGenerator.FillTrianglesQuads(ref currentSubmesh.triangles, ref currentSubmesh.triangleCount, ref currentSubmesh.firstVertex, submeshInstruction.firstVertexIndex, instructionTriangleCount, (i == last)); - } - - } - - thisSubmeshMaterials.Add(submeshInstruction.material); - } - - currentMesh.subMeshCount = submeshCount; - - for (int i = 0; i < submeshCount; ++i) - currentMesh.SetTriangles(submeshes.Items[i].triangles, i); + if (SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed)) { // This thorough structure check is cheaper than updating triangles every frame. + meshGenerator.FillTriangles(currentMesh); + meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedShaderdMaterialsArray(); + } else if (rendererBuffers.MaterialsChangedInLastUpdate()) { + meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedShaderdMaterialsArray(); } - #if SPINE_OPTIONAL_SOLVETANGENTS - if (calculateTangents) { - ArraysMeshGenerator.SolveTangents2DEnsureSize(ref this.tangents, ref this.tempTanBuffer, vertices.Length); - for (int i = 0; i < submeshCount; i++) { - var submesh = submeshes.Items[i]; - ArraysMeshGenerator.SolveTangents2DTriangles(this.tempTanBuffer, submesh.triangles, submesh.triangleCount, this.vertices, this.uvs, vertexCount); - } - ArraysMeshGenerator.SolveTangents2DBuffer(this.tangents, this.tempTanBuffer, vertexCount); - currentMesh.tangents = this.tangents; - } - #endif - - // CheckIfMustUpdateMaterialArray (last pushed materials vs currently parsed materials) - // Needs to check against the Working Submesh Instructions Materials instead of the cached submeshMaterials. - { - var lastPushedMaterials = this.sharedMaterials; - bool mustUpdateRendererMaterials = mustUpdateMeshStructure || - (lastPushedMaterials.Length != submeshCount); - - // Assumption at this point: (lastPushedMaterials.Count == workingSubmeshInstructions.Count == thisSubmeshMaterials.Count == submeshCount) - - // Case: mesh structure or submesh count did not change but materials changed. - if (!mustUpdateRendererMaterials) { - var workingSubmeshInstructionsItems = workingSubmeshInstructions.Items; - for (int i = 0; i < submeshCount; i++) { - if (lastPushedMaterials[i].GetInstanceID() != workingSubmeshInstructionsItems[i].material.GetInstanceID()) { // Bounds check is implied by submeshCount above. - mustUpdateRendererMaterials = true; - { - var thisSubmeshMaterials = this.submeshMaterials.Items; - if (mustUpdateRendererMaterials) - for (int j = 0; j < submeshCount; j++) - thisSubmeshMaterials[j] = workingSubmeshInstructionsItems[j].material; - } - break; - } - } - } - - if (mustUpdateRendererMaterials) { - if (submeshMaterials.Count == sharedMaterials.Length) - submeshMaterials.CopyTo(sharedMaterials); - else - sharedMaterials = submeshMaterials.ToArray(); - - meshRenderer.sharedMaterials = sharedMaterials; - } - } - - // Step 4. The UnityEngine.Mesh is ready. Set it as the MeshFilter's mesh. Store the instructions used for that mesh. ============================================================ meshFilter.sharedMesh = currentMesh; - currentSmartMesh.instructionUsed.Set(workingInstruction); - - } - - static bool CheckIfMustUpdateMeshStructure (SmartMesh.Instruction a, SmartMesh.Instruction b) { - - #if UNITY_EDITOR - if (!Application.isPlaying) - return true; - #endif - - if (a.vertexCount != b.vertexCount) - return true; - - if (a.immutableTriangles != b.immutableTriangles) - return true; - - int attachmentCountB = b.attachments.Count; - if (a.attachments.Count != attachmentCountB) // Bounds check for the looped storedAttachments count below. - return true; - - var attachmentsA = a.attachments.Items; - var attachmentsB = b.attachments.Items; - for (int i = 0; i < attachmentCountB; i++) { - if (attachmentsA[i] != attachmentsB[i]) - return true; - } - - // Submesh count changed - int submeshCountA = a.submeshInstructions.Count; - int submeshCountB = b.submeshInstructions.Count; - if (submeshCountA != submeshCountB) - return true; - - // Submesh Instruction mismatch - var submeshInstructionsItemsA = a.submeshInstructions.Items; - var submeshInstructionsItemsB = b.submeshInstructions.Items; - for (int i = 0; i < submeshCountB; i++) { - var submeshA = submeshInstructionsItemsA[i]; - var submeshB = submeshInstructionsItemsB[i]; - - if (!( - submeshA.vertexCount == submeshB.vertexCount && - submeshA.startSlot == submeshB.startSlot && - submeshA.endSlot == submeshB.endSlot && - submeshA.triangleCount == submeshB.triangleCount && - submeshA.firstVertexIndex == submeshB.firstVertexIndex - )) - return true; - } - - return false; - } - - ///This is a Mesh that also stores the instructions SkeletonRenderer generated for it. - public class SmartMesh : System.IDisposable { - public Mesh mesh = Spine.Unity.SpineMesh.NewMesh(); - public SmartMesh.Instruction instructionUsed = new SmartMesh.Instruction(); - - public void Dispose () { - if (mesh != null) { - #if UNITY_EDITOR - if (Application.isEditor && !Application.isPlaying) - UnityEngine.Object.DestroyImmediate(mesh); - else - UnityEngine.Object.Destroy(mesh); - #else - UnityEngine.Object.Destroy(mesh); - #endif - } - mesh = null; - } - - public class Instruction { - public bool immutableTriangles; - public int vertexCount = -1; - public readonly ExposedList attachments = new ExposedList(); - public readonly ExposedList submeshInstructions = new ExposedList(); - - public void Clear () { - this.attachments.Clear(false); - this.submeshInstructions.Clear(false); - } - - public void Set (Instruction other) { - this.immutableTriangles = other.immutableTriangles; - this.vertexCount = other.vertexCount; - - this.attachments.Clear(false); - this.attachments.GrowIfNeeded(other.attachments.Capacity); - this.attachments.Count = other.attachments.Count; - other.attachments.CopyTo(this.attachments.Items); - - this.submeshInstructions.Clear(false); - this.submeshInstructions.GrowIfNeeded(other.submeshInstructions.Capacity); - this.submeshInstructions.Count = other.submeshInstructions.Count; - other.submeshInstructions.CopyTo(this.submeshInstructions.Items); - } - } + currentSmartMesh.instructionUsed.Set(currentInstructions); } } }