From 77c4cdc1e4033444642e6fa63bfdfd9d55d3c827 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 21 Jul 2016 22:23:21 +0800 Subject: [PATCH] [unity] SkeletonRenderer Update (Tangents, MeshGeneration) (#646) --- .../Editor/SkeletonRendererInspector.cs | 77 ++-- .../Arrays/ArraysMeshGenerator.cs | 219 ++++++++--- .../Arrays/ArraysSimpleMeshGenerator.cs | 21 +- .../Arrays/ArraysSubmeshSetMeshGenerator.cs | 28 +- .../Arrays/ArraysSubmeshedMeshGenerator.cs | 44 +-- .../Mesh Generation/ISimpleMeshGenerator.cs | 7 +- .../ISubmeshedMeshGenerator.cs | 9 +- .../SkeletonRenderSeparator.cs | 20 +- .../SkeletonRenderSeparator.txt | 6 + .../SkeletonRenderSeparator.txt.meta | 8 + .../Assets/spine-unity/SkeletonRenderer.cs | 344 ++++-------------- 11 files changed, 374 insertions(+), 409 deletions(-) create mode 100644 spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.txt create mode 100644 spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.txt.meta diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs b/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs index af3b15fae..d79c6eef5 100644 --- a/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs +++ b/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs @@ -30,7 +30,6 @@ *****************************************************************************/ #define NO_PREFAB_MESH -using System; using UnityEditor; using UnityEngine; @@ -65,23 +64,24 @@ namespace Spine.Unity.Editor { } protected virtual void DrawInspectorGUI () { + // JOHN: todo: support multiediting. SkeletonRenderer component = (SkeletonRenderer)target; - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.PropertyField(skeletonDataAsset); - const string ReloadButtonLabel = "Reload"; - float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20; - if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) { - if (component.skeletonDataAsset != null) { - foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) { - if (aa != null) - aa.Reset(); + using (new EditorGUILayout.HorizontalScope()) { + EditorGUILayout.PropertyField(skeletonDataAsset); + const string ReloadButtonLabel = "Reload"; + float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20; + if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) { + if (component.skeletonDataAsset != null) { + foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) { + if (aa != null) + aa.Reset(); + } + component.skeletonDataAsset.Reset(); } - component.skeletonDataAsset.Reset(); + component.Initialize(true); } - component.Initialize(true); } - EditorGUILayout.EndHorizontal(); if (!component.valid) { component.Initialize(true); @@ -100,10 +100,10 @@ namespace Spine.Unity.Editor { // Initial skin name. { - String[] skins = new String[component.skeleton.Data.Skins.Count]; + string[] skins = new string[component.skeleton.Data.Skins.Count]; int skinIndex = 0; for (int i = 0; i < skins.Length; i++) { - String skinNameString = component.skeleton.Data.Skins.Items[i].Name; + string skinNameString = component.skeleton.Data.Skins.Items[i].Name; skins[i] = skinNameString; if (skinNameString == initialSkinName.stringValue) skinIndex = i; @@ -115,40 +115,33 @@ namespace Spine.Unity.Editor { EditorGUILayout.Space(); // Sorting Layers - { - SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true); - } + SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true); // More Render Options... - { - using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { + using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { + EditorGUI.indentLevel++; + advancedFoldout = EditorGUILayout.Foldout(advancedFoldout, "Advanced"); + if (advancedFoldout) { EditorGUI.indentLevel++; - advancedFoldout = EditorGUILayout.Foldout(advancedFoldout, "Advanced"); - if (advancedFoldout) { - EditorGUI.indentLevel++; - SeparatorsField(separatorSlotNames); - EditorGUILayout.PropertyField(meshes, - new GUIContent("Render Mesh Attachments", "Disable to optimize rendering for skeletons that don't use Mesh Attachments")); - EditorGUILayout.PropertyField(immutableTriangles, - new GUIContent("Immutable Triangles", "Enable to optimize rendering for skeletons that never change attachment visbility")); - EditorGUILayout.Space(); + SeparatorsField(separatorSlotNames); + EditorGUILayout.PropertyField(meshes, + new GUIContent("Render MeshAttachments", "Disable to optimize rendering for skeletons that don't use Mesh Attachments")); + EditorGUILayout.PropertyField(immutableTriangles, + new GUIContent("Immutable Triangles", "Enable to optimize rendering for skeletons that never change attachment visbility")); + EditorGUILayout.Space(); - const float MinZSpacing = -0.1f; - const float MaxZSpacing = 0f; - EditorGUILayout.Slider(zSpacing, MinZSpacing, MaxZSpacing); + const float MinZSpacing = -0.1f; + const float MaxZSpacing = 0f; + EditorGUILayout.Slider(zSpacing, MinZSpacing, MaxZSpacing); - // Optional fields. May be disabled in SkeletonRenderer. - if (normals != null) { - EditorGUILayout.PropertyField(normals); - EditorGUILayout.PropertyField(tangents); - } - if (frontFacing != null) - EditorGUILayout.PropertyField(frontFacing); + // Optional fields. May be disabled in SkeletonRenderer. + if (normals != null) EditorGUILayout.PropertyField(normals, new GUIContent("Add Normals")); + if (tangents != null) EditorGUILayout.PropertyField(tangents, new GUIContent("Solve Tangents")); + if (frontFacing != null) EditorGUILayout.PropertyField(frontFacing); - EditorGUI.indentLevel--; - } EditorGUI.indentLevel--; } + EditorGUI.indentLevel--; } } @@ -162,7 +155,7 @@ namespace Spine.Unity.Editor { } override public void OnInspectorGUI () { - serializedObject.Update(); + //serializedObject.Update(); DrawInspectorGUI(); if (serializedObject.ApplyModifiedProperties() || (UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed") 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 e5e9811a2..f9a522c37 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysMeshGenerator.cs @@ -34,8 +34,11 @@ using UnityEngine; namespace Spine.Unity.MeshGeneration { public class ArraysMeshGenerator { #region Settings - protected bool premultiplyVertexColors = true; - public bool PremultiplyVertexColors { get { return this.premultiplyVertexColors; } set { this.premultiplyVertexColors = value; } } + public bool PremultiplyVertexColors { get; set; } + protected bool addNormals; + public bool AddNormals { get { return addNormals; } set { addNormals = value; } } + protected bool addTangents { get; set; } + public bool AddTangents { get { return addTangents; } set { addTangents = value; } } #endregion protected float[] attachmentVertexBuffer = new float[8]; @@ -43,25 +46,22 @@ namespace Spine.Unity.MeshGeneration { protected Color32[] meshColors32; protected Vector2[] meshUVs; - - protected bool generateNormals = false; - public bool GenerateNormals { - get { return generateNormals; } - set { generateNormals = value; } - } - - Vector3[] meshNormals; + #if SPINE_OPTIONAL_NORMALS + protected Vector3[] meshNormals; + #endif + protected Vector4[] meshTangents; + protected Vector2[] tempTanBuffer; public void TryAddNormalsTo (Mesh mesh, int targetVertexCount) { #if SPINE_OPTIONAL_NORMALS - if (generateNormals) { - bool verticesWasResized = this.meshNormals == null || targetVertexCount > meshNormals.Length; + if (addNormals) { + bool verticesWasResized = this.meshNormals == null || meshNormals.Length < targetVertexCount; if (verticesWasResized) { this.meshNormals = new Vector3[targetVertexCount]; - Vector3 normal = new Vector3(0, 0, -1); + Vector3 fixedNormal = new Vector3(0, 0, -1f); Vector3[] normals = this.meshNormals; for (int i = 0; i < targetVertexCount; i++) - normals[i] = normal; + normals[i] = fixedNormal; } mesh.normals = this.meshNormals; @@ -69,7 +69,8 @@ namespace Spine.Unity.MeshGeneration { #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; @@ -97,12 +98,10 @@ namespace Spine.Unity.MeshGeneration { return submeshBuffersWasResized; } - /// - /// Fills vertex arrays. - /// + /// 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. + /// 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. @@ -112,7 +111,8 @@ namespace Spine.Unity.MeshGeneration { /// A temporary vertex position buffer for attachment position values. /// Reference to the running calculated minimum bounds. /// Reference to the running calculated maximum bounds. - 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) { + /// 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; @@ -183,7 +183,7 @@ namespace Spine.Unity.MeshGeneration { else if (y4 > bmax.y) bmax.y = y4; vi += 4; - } else { + } else if (renderMeshes) { var meshAttachment = attachment as MeshAttachment; if (meshAttachment != null) { int meshVertexCount = meshAttachment.worldVerticesLength; @@ -229,22 +229,18 @@ namespace Spine.Unity.MeshGeneration { } - /// - /// Fills a submesh triangle buffer array. - /// + /// 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. - /// The current triangle count of the submesh buffer. This is not always equal to triangleBuffer.Length because for last submeshes, length may be larger than needed. /// If set to true, the triangle buffer is allowed to be larger than needed. - public static void FillTriangles (Skeleton skeleton, int triangleCount, int firstVertex, int startSlot, int endSlot, ref int[] triangleBuffer, bool isLastSubmesh) { + public static void FillTriangles (ref int[] triangleBuffer, Skeleton skeleton, int triangleCount, int firstVertex, int startSlot, int endSlot, bool isLastSubmesh) { int trianglesCapacity = triangleBuffer.Length; - var tris = triangleBuffer; + int[] tris = triangleBuffer; - // Ensure triangleBuffer size. if (isLastSubmesh) { if (trianglesCapacity > triangleCount) { for (int i = triangleCount; i < trianglesCapacity; i++) @@ -256,45 +252,170 @@ namespace Spine.Unity.MeshGeneration { triangleBuffer = tris = new int[triangleCount]; } - // Iterate through submesh slots and store the triangles. - int triangleIndex = 0; - int afv = firstVertex; // attachment first vertex var skeletonDrawOrderItems = skeleton.drawOrder.Items; - for (int i = startSlot, n = endSlot; i < n; i++) { + for (int i = startSlot, n = endSlot, ti = 0, afv = firstVertex; i < n; i++) { var attachment = skeletonDrawOrderItems[i].attachment; + // RegionAttachment if (attachment is RegionAttachment) { - tris[triangleIndex] = afv; tris[triangleIndex + 1] = afv + 2; tris[triangleIndex + 2] = afv + 1; - tris[triangleIndex + 3] = afv + 2; tris[triangleIndex + 4] = afv + 3; tris[triangleIndex + 5] = afv + 1; - triangleIndex += 6; + 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; - } else { - int[] attachmentTriangles; - int attachmentVertexCount; - var meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - attachmentVertexCount = meshAttachment.worldVerticesLength >> 1; // length/2 - attachmentTriangles = meshAttachment.triangles; - for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, triangleIndex++) - tris[triangleIndex] = afv + attachmentTriangles[ii]; - - afv += attachmentVertexCount; - } + continue; } - } // Done adding current submesh triangles + + // 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); Vector3 center = boundsMin + size * 0.5f; return new Bounds(center, 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; + 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]; 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 de388d3c5..6c5f76024 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSimpleMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSimpleMeshGenerator.cs @@ -34,18 +34,15 @@ 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 Scale { get { return scale; } set { scale = value; } } + public float ZSpacing { get; set; } #endregion - private Mesh lastGeneratedMesh; + protected Mesh lastGeneratedMesh; public Mesh LastGeneratedMesh { get { return lastGeneratedMesh; } } readonly DoubleBufferedMesh doubleBufferedMesh = new DoubleBufferedMesh(); int[] triangles; - int triangleBufferCount; public Mesh GenerateMesh (Skeleton skeleton) { int totalVertexCount = 0; // size of vertex arrays @@ -80,7 +77,6 @@ namespace Spine.Unity.MeshGeneration { this.triangles = this.triangles ?? new int[totalTriangleCount]; // STEP 3 : Update vertex buffer - const float zSpacing = 0; 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; @@ -96,7 +92,7 @@ namespace Spine.Unity.MeshGeneration { meshBoundsMax.z = zFauxHalfThickness * scale; int vertexIndex = 0; - ArraysMeshGenerator.FillVerts(skeleton, 0, drawOrderCount, zSpacing, this.premultiplyVertexColors, this.meshVertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax); + 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; @@ -111,7 +107,7 @@ namespace Spine.Unity.MeshGeneration { } // Step 4 : Update Triangles buffer - ArraysMeshGenerator.FillTriangles(skeleton, totalTriangleCount, 0, 0, drawOrderCount, ref this.triangles, true); + ArraysMeshGenerator.FillTriangles(ref this.triangles, skeleton, totalTriangleCount, 0, 0, drawOrderCount, true); // Step 5 : Update Mesh with buffers var mesh = doubleBufferedMesh.GetNextMesh(); @@ -120,6 +116,13 @@ namespace Spine.Unity.MeshGeneration { mesh.uv = meshUVs; mesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax); mesh.triangles = triangles; + 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 eca7b8d40..bccdfc3b0 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshSetMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshSetMeshGenerator.cs @@ -28,13 +28,12 @@ * 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 ArraysSubmeshSetMeshGenerator : ArraysMeshGenerator, ISubmeshSetMeshGenerator { #region Settings - public float zSpacing = 0f; + public float ZSpacing { get; set; } #endregion readonly DoubleBuffered doubleBufferedSmartMesh = new DoubleBuffered(); @@ -69,7 +68,7 @@ namespace Spine.Unity.MeshGeneration { // Initial values for manual Mesh Bounds calculation Vector3 meshBoundsMin; Vector3 meshBoundsMax; - float zSpacing = this.zSpacing; + float zSpacing = this.ZSpacing; if (vertexCount <= 0) { meshBoundsMin = new Vector3(0, 0, 0); meshBoundsMax = new Vector3(0, 0, 0); @@ -103,7 +102,7 @@ namespace Spine.Unity.MeshGeneration { var ca = skeletonDrawOrderItems[i].attachment; if (ca != null) workingAttachments.Add(ca); // Includes BoundingBoxes. This is ok. } - ArraysMeshGenerator.FillVerts(skeleton, startSlot, endSlot, zSpacing, this.premultiplyVertexColors, this.meshVertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax); + 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); @@ -112,7 +111,9 @@ namespace Spine.Unity.MeshGeneration { if (structureDoesntMatch) { var currentBuffer = submeshBuffers.Items[submeshIndex]; bool isLastSubmesh = (submeshIndex == submeshCount - 1); - ArraysMeshGenerator.FillTriangles(currentInstruction.skeleton, currentInstruction.triangleCount, currentInstruction.firstVertexIndex, currentInstruction.startSlot, currentInstruction.endSlot, ref currentBuffer.triangles, isLastSubmesh); + ArraysMeshGenerator.FillTriangles(ref currentBuffer.triangles, currentInstruction.skeleton, currentInstruction.triangleCount, currentInstruction.firstVertexIndex, currentInstruction.startSlot, currentInstruction.endSlot, isLastSubmesh); + currentBuffer.triangleCount = currentInstruction.triangleCount; + currentBuffer.firstVertex = currentInstruction.firstVertexIndex; } } @@ -124,15 +125,26 @@ namespace Spine.Unity.MeshGeneration { // 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 SPINE_OPTIONAL_NORMALS - this.TryAddNormalsTo(mesh, vertexCount); - #endif + 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); 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 a7c48a207..998a18cf5 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshedMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/Arrays/ArraysSubmeshedMeshGenerator.cs @@ -28,7 +28,6 @@ * 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; using System.Collections.Generic; @@ -42,11 +41,9 @@ namespace Spine.Unity.MeshGeneration { readonly List separators = new List(); public List Separators { get { return this.separators; } } - public float zSpacing = 0f; - #if SPINE_OPTIONAL_NORMALS - public bool generateNormals; - public bool generateTangents; - #endif + #region Settings + public float ZSpacing { get; set; } + #endregion readonly DoubleBuffered doubleBufferedSmartMesh = new DoubleBuffered(); readonly SubmeshedMeshInstruction currentInstructions = new SubmeshedMeshInstruction(); @@ -158,12 +155,13 @@ namespace Spine.Unity.MeshGeneration { 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(meshInstructions.vertexCount, ref this.meshVertices, ref this.meshUVs, ref this.meshColors32); + bool vertBufferResized = ArraysMeshGenerator.EnsureSize(vertexCount, ref this.meshVertices, ref this.meshUVs, ref this.meshColors32); Vector3[] vertices = this.meshVertices; // STEP 2: Update buffers based on Skeleton. - float zSpacing = this.zSpacing; + float zSpacing = this.ZSpacing; Vector3 meshBoundsMin; Vector3 meshBoundsMax; int attachmentCount = meshInstructions.attachmentList.Count; @@ -192,11 +190,13 @@ namespace Spine.Unity.MeshGeneration { int start = submeshInstruction.startSlot; int end = submeshInstruction.endSlot; var skeleton = submeshInstruction.skeleton; - ArraysMeshGenerator.FillVerts(skeleton, start, end, zSpacing, this.premultiplyVertexColors, vertices, this.meshUVs, this.meshColors32, ref vertexIndex, ref this.attachmentVertexBuffer, ref meshBoundsMin, ref meshBoundsMax); + 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(skeleton, submeshInstruction.triangleCount, submeshInstruction.firstVertexIndex, start, end, ref currentBuffer.triangles, isLastSubmesh); + ArraysMeshGenerator.FillTriangles(ref currentBuffer.triangles, skeleton, submeshInstruction.triangleCount, submeshInstruction.firstVertexIndex, start, end, isLastSubmesh); + currentBuffer.triangleCount = submeshInstruction.triangleCount; + currentBuffer.firstVertex = submeshInstruction.firstVertexIndex; } } @@ -215,24 +215,16 @@ namespace Spine.Unity.MeshGeneration { for (int i = 0; i < submeshCount; i++) mesh.SetTriangles(submeshBuffers.Items[i].triangles, i); - #if SPINE_OPTIONAL_NORMALS - if (generateNormals) { - int vertexCount = meshInstructions.vertexCount; - Vector3[] normals = new Vector3[vertexCount]; - Vector3 normal = new Vector3(0, 0, -1); - for (int i = 0; i < vertexCount; i++) - normals[i] = normal; - mesh.normals = normals; + TryAddNormalsTo(mesh, vertexCount); + } - if (generateTangents) { - Vector4[] tangents = new Vector4[vertexCount]; - Vector4 tangent = new Vector4(1, 0, 0, -1); - for (int i = 0; i < vertexCount; i++) - tangents[i] = tangent; - mesh.tangents = tangents; - } + 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); } - #endif + SolveTangents2DBuffer(this.meshTangents, this.tempTanBuffer, vertexCount); } return new MeshAndMaterials(smartMesh.mesh, sharedMaterials); diff --git a/spine-unity/Assets/spine-unity/Mesh Generation/ISimpleMeshGenerator.cs b/spine-unity/Assets/spine-unity/Mesh Generation/ISimpleMeshGenerator.cs index 1fdb9e67a..07d5a5da0 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/ISimpleMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/ISimpleMeshGenerator.cs @@ -4,8 +4,13 @@ // The Scale property allows generated mesh to match external systems like Canvas referencePixelsPerUnit public interface ISimpleMeshGenerator { - float Scale { set; } UnityEngine.Mesh GenerateMesh (Spine.Skeleton skeleton); UnityEngine.Mesh LastGeneratedMesh { get; } + + float Scale { set; } + float ZSpacing { get; set; } + + bool AddNormals { get; set; } + bool AddTangents { 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 285653ad7..64d13d675 100644 --- a/spine-unity/Assets/spine-unity/Mesh Generation/ISubmeshedMeshGenerator.cs +++ b/spine-unity/Assets/spine-unity/Mesh Generation/ISubmeshedMeshGenerator.cs @@ -12,6 +12,10 @@ namespace Spine.Unity.MeshGeneration { SubmeshedMeshInstruction GenerateInstruction (Skeleton skeleton); MeshAndMaterials GenerateMesh (SubmeshedMeshInstruction wholeMeshInstruction); List Separators { get; } + + float ZSpacing { get; set; } + bool AddNormals { get; set; } + bool AddTangents { get; set; } } // ISubmeshSetMeshGenerator @@ -22,7 +26,10 @@ namespace Spine.Unity.MeshGeneration { // Step 4: Put the Mesh in MeshFilter. Put the Materials in MeshRenderer.sharedMaterials. public interface ISubmeshSetMeshGenerator { MeshAndMaterials GenerateMesh (ExposedList instructions, int startSubmesh, int endSubmesh); - bool GenerateNormals { get; set; } + + float ZSpacing { get; set; } + bool AddNormals { get; set; } + bool AddTangents { get; set; } } /// Primarily a collection of Submesh Instructions. This constitutes instructions for how to construct a mesh containing submeshes. diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs index e56b84732..258272735 100644 --- a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.cs @@ -69,7 +69,7 @@ namespace Spine.Unity.Modules { void OnEnable () { if (skeletonRenderer == null) return; - if (block == null) block = new MaterialPropertyBlock(); + if (copiedBlock == null) copiedBlock = new MaterialPropertyBlock(); mainMeshRenderer = skeletonRenderer.GetComponent(); skeletonRenderer.GenerateMeshOverride -= HandleRender; @@ -103,7 +103,7 @@ namespace Spine.Unity.Modules { s.ClearMesh(); } - MaterialPropertyBlock block; + MaterialPropertyBlock copiedBlock; void HandleRender (SkeletonRenderer.SmartMesh.Instruction instruction) { int rendererCount = partsRenderers.Count; @@ -112,21 +112,27 @@ namespace Spine.Unity.Modules { int rendererIndex = 0; if (copyPropertyBlock) - mainMeshRenderer.GetPropertyBlock(block); + mainMeshRenderer.GetPropertyBlock(copiedBlock); var submeshInstructions = instruction.submeshInstructions; var submeshInstructionsItems = submeshInstructions.Items; int lastSubmeshInstruction = submeshInstructions.Count - 1; var currentRenderer = partsRenderers[rendererIndex]; - bool useNormals = skeletonRenderer.calculateNormals; + bool addNormals = skeletonRenderer.calculateNormals; + bool addTangents = skeletonRenderer.calculateTangents; for (int si = 0, start = 0; si <= lastSubmeshInstruction; si++) { if (submeshInstructionsItems[si].forceSeparate || si == lastSubmeshInstruction) { - currentRenderer.RenderParts(instruction.submeshInstructions, start, si + 1); - currentRenderer.MeshGenerator.GenerateNormals = useNormals; + // Apply properties + var meshGenerator = currentRenderer.MeshGenerator; + meshGenerator.AddNormals = addNormals; + meshGenerator.AddTangents = addTangents; if (copyPropertyBlock) - currentRenderer.SetPropertyBlock(block); + currentRenderer.SetPropertyBlock(copiedBlock); + + // Render + currentRenderer.RenderParts(instruction.submeshInstructions, start, si + 1); start = si + 1; rendererIndex++; diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.txt b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.txt new file mode 100644 index 000000000..c42f929ac --- /dev/null +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.txt @@ -0,0 +1,6 @@ +SkeletonRenderSeparator +======================= + +Dependencies: +- SkeletonPartsRenderer uses the `ArraysMeshGenerator` class in `Spine.Unity.MeshGeneration` +- It requires `SPINE_OPTIONAL_RENDEROVERRIDE` to be #defined in `SkeletonRenderer.cs`. diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.txt.meta b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.txt.meta new file mode 100644 index 000000000..5dd3c994d --- /dev/null +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonRenderSeparator/SkeletonRenderSeparator.txt.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f0e413eeb00eabc46bde6dbd7aaaa76c +timeCreated: 1469110129 +licenseType: Free +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/spine-unity/SkeletonRenderer.cs b/spine-unity/Assets/spine-unity/SkeletonRenderer.cs index 94607c555..a3bb70300 100644 --- a/spine-unity/Assets/spine-unity/SkeletonRenderer.cs +++ b/spine-unity/Assets/spine-unity/SkeletonRenderer.cs @@ -28,9 +28,12 @@ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -#define SPINE_OPTIONAL_NORMALS + #define SPINE_OPTIONAL_RENDEROVERRIDE #define SPINE_OPTIONAL_MATERIALOVERRIDE +#define SPINE_OPTIONAL_NORMALS +#define SPINE_OPTIONAL_SOLVETANGENTS + //#define SPINE_OPTIONAL_FRONTFACING //#define SPINE_OPTIONAL_SUBMESHRENDERER // Deprecated @@ -62,8 +65,12 @@ namespace Spine.Unity { public float zSpacing; public bool renderMeshes = true, immutableTriangles; public bool pmaVertexColors = true; + #if SPINE_OPTIONAL_NORMALS - public bool calculateNormals, calculateTangents; + public bool calculateNormals; + #endif + #if SPINE_OPTIONAL_SOLVETANGENTS + public bool calculateTangents; #endif #if SPINE_OPTIONAL_FRONTFACING public bool frontFacing; @@ -115,7 +122,7 @@ namespace Spine.Unity { Spine.Unity.DoubleBuffered doubleBufferedMesh; readonly SmartMesh.Instruction currentInstructions = new SmartMesh.Instruction(); - readonly ExposedList submeshes = new ExposedList(); + readonly ExposedList submeshes = new ExposedList(); readonly ExposedList submeshMaterials = new ExposedList(); Material[] sharedMaterials = new Material[0]; float[] tempVertices = new float[8]; @@ -124,7 +131,10 @@ namespace Spine.Unity { Vector2[] uvs; #if SPINE_OPTIONAL_NORMALS Vector3[] normals; + #endif + #if SPINE_OPTIONAL_SOLVETANGENTS Vector4[] tangents; + Vector2[] tempTanBuffer; #endif #region Runtime Instantiation @@ -189,7 +199,7 @@ namespace Spine.Unity { vertices = new Vector3[0]; skeleton = new Skeleton(skeletonData); - if (initialSkinName != null && initialSkinName.Length > 0 && initialSkinName != "default") + if (!string.IsNullOrEmpty(initialSkinName) && initialSkinName != "default") skeleton.SetSkin(initialSkinName); separatorSlots.Clear(); @@ -211,27 +221,18 @@ namespace Spine.Unity { return; if ( - ( - !meshRenderer.enabled - - ) + (!meshRenderer.enabled) #if SPINE_OPTIONAL_RENDEROVERRIDE && this.generateMeshOverride == null #endif - #if SPINE_OPTIONAL_SUBMESHRENDERER && submeshRenderers.Length > 0 #endif - ) return; + - - - // STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. - - // This method caches several .Items arrays. - // Never mutate their overlying ExposedList objects. + // STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. ============================================================ ExposedList drawOrder = skeleton.drawOrder; var drawOrderItems = drawOrder.Items; int drawOrderCount = drawOrder.Count; @@ -267,7 +268,6 @@ namespace Spine.Unity { for (int i = 0; i < drawOrderCount; i++) { Slot slot = drawOrderItems[i]; Attachment attachment = slot.attachment; - workingAttachmentsItems[i] = attachment; #if SPINE_OPTIONAL_FRONTFACING @@ -357,7 +357,8 @@ namespace Spine.Unity { workingInstruction.frontFacing = this.frontFacing; #endif - // STEP 1.9. Post-process workingInstructions. + + // 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 @@ -374,7 +375,6 @@ namespace Spine.Unity { } } #endif - #if SPINE_OPTIONAL_RENDEROVERRIDE if (this.generateMeshOverride != null) { this.generateMeshOverride(workingInstruction); @@ -382,50 +382,22 @@ namespace Spine.Unity { } #endif - // STEP 2. Update vertex buffer based on verts from the attachments. + + // STEP 2. Update vertex buffer based on verts from the attachments. ============================================================ // Uses values that were also stored in workingInstruction. - Vector3[] vertices = this.vertices; - bool vertexCountIncreased = vertexCount > vertices.Length; - - if (vertexCountIncreased) { - this.vertices = vertices = new Vector3[vertexCount]; - this.colors = new Color32[vertexCount]; - this.uvs = new Vector2[vertexCount]; - - #if SPINE_OPTIONAL_NORMALS - if (calculateNormals) { - Vector3[] localNormals = this.normals = new Vector3[vertexCount]; - Vector3 normal = new Vector3(0, 0, -1); - for (int i = 0; i < vertexCount; i++) - localNormals[i] = normal; - } - - // For dynamic tangent calculation, you can remove the tangent-filling logic and add tangent calculation logic below. - if (calculateTangents) { - Vector4[] localTangents = this.tangents = new Vector4[vertexCount]; - Vector4 tangent = new Vector4(1, 0, 0, -1); - for (int i = 0; i < vertexCount; i++) - localTangents[i] = tangent; - } - #endif - } else { - Vector3 zero = Vector3.zero; - for (int i = vertexCount, n = vertices.Length; i < n; i++) - vertices[i] = zero; + bool vertexCountIncreased = ArraysMeshGenerator.EnsureSize(vertexCount, ref this.vertices, ref this.uvs, ref this.colors); + #if SPINE_OPTIONAL_NORMALS + if (vertexCountIncreased && calculateNormals) { + Vector3[] localNormals = this.normals = new Vector3[vertexCount]; + Vector3 normal = new Vector3(0, 0, -1); + for (int i = 0; i < vertexCount; i++) + localNormals[i] = normal; } - - float zSpacing = this.zSpacing; - float[] tempVertices = this.tempVertices; - Vector2[] uvs = this.uvs; - Color32[] colors = this.colors; - int vertexIndex = 0; - bool pmaVertexColors = this.pmaVertexColors; - Color32 color; - float a = skeleton.a * 255, r = skeleton.r, g = skeleton.g, b = skeleton.b; + #endif Vector3 meshBoundsMin; Vector3 meshBoundsMax; - if (vertexCount == 0) { + if (vertexCount <= 0) { meshBoundsMin = new Vector3(0, 0, 0); meshBoundsMax = new Vector3(0, 0, 0); } else { @@ -433,6 +405,7 @@ namespace Spine.Unity { meshBoundsMin.y = int.MaxValue; meshBoundsMax.x = int.MinValue; meshBoundsMax.y = int.MinValue; + if (zSpacing > 0f) { meshBoundsMin.z = 0f; meshBoundsMax.z = zSpacing * (drawOrderCount - 1); @@ -440,192 +413,60 @@ namespace Spine.Unity { meshBoundsMin.z = zSpacing * (drawOrderCount - 1); meshBoundsMax.z = 0f; } - int i = 0; - do { - Slot slot = drawOrderItems[i]; - Attachment attachment = slot.attachment; - RegionAttachment regionAttachment = attachment as RegionAttachment; - if (regionAttachment != null) { - regionAttachment.ComputeWorldVertices(slot.bone, tempVertices); - - float z = i * zSpacing; - float x1 = tempVertices[RegionAttachment.X1], y1 = tempVertices[RegionAttachment.Y1]; - float x2 = tempVertices[RegionAttachment.X2], y2 = tempVertices[RegionAttachment.Y2]; - float x3 = tempVertices[RegionAttachment.X3], y3 = tempVertices[RegionAttachment.Y3]; - float x4 = tempVertices[RegionAttachment.X4], y4 = tempVertices[RegionAttachment.Y4]; - vertices[vertexIndex].x = x1; - vertices[vertexIndex].y = y1; - vertices[vertexIndex].z = z; - vertices[vertexIndex + 1].x = x4; - vertices[vertexIndex + 1].y = y4; - vertices[vertexIndex + 1].z = z; - vertices[vertexIndex + 2].x = x2; - vertices[vertexIndex + 2].y = y2; - vertices[vertexIndex + 2].z = z; - vertices[vertexIndex + 3].x = x3; - vertices[vertexIndex + 3].y = y3; - vertices[vertexIndex + 3].z = z; - - if (pmaVertexColors) { - color.a = (byte)(a * slot.a * regionAttachment.a); - color.r = (byte)(r * slot.r * regionAttachment.r * color.a); - color.g = (byte)(g * slot.g * regionAttachment.g * color.a); - color.b = (byte)(b * slot.b * regionAttachment.b * color.a); - if (slot.data.blendMode == BlendMode.additive) color.a = 0; - } else { - color.a = (byte)(a * slot.a * regionAttachment.a); - color.r = (byte)(r * slot.r * regionAttachment.r * 255); - color.g = (byte)(g * slot.g * regionAttachment.g * 255); - color.b = (byte)(b * slot.b * regionAttachment.b * 255); - } - - colors[vertexIndex] = color; - colors[vertexIndex + 1] = color; - colors[vertexIndex + 2] = color; - colors[vertexIndex + 3] = color; - - float[] regionUVs = regionAttachment.uvs; - uvs[vertexIndex].x = regionUVs[RegionAttachment.X1]; - uvs[vertexIndex].y = regionUVs[RegionAttachment.Y1]; - uvs[vertexIndex + 1].x = regionUVs[RegionAttachment.X4]; - uvs[vertexIndex + 1].y = regionUVs[RegionAttachment.Y4]; - uvs[vertexIndex + 2].x = regionUVs[RegionAttachment.X2]; - uvs[vertexIndex + 2].y = regionUVs[RegionAttachment.Y2]; - uvs[vertexIndex + 3].x = regionUVs[RegionAttachment.X3]; - uvs[vertexIndex + 3].y = regionUVs[RegionAttachment.Y3]; - - // Calculate min/max X - if (x1 < meshBoundsMin.x) - meshBoundsMin.x = x1; - else if (x1 > meshBoundsMax.x) - meshBoundsMax.x = x1; - if (x2 < meshBoundsMin.x) - meshBoundsMin.x = x2; - else if (x2 > meshBoundsMax.x) - meshBoundsMax.x = x2; - if (x3 < meshBoundsMin.x) - meshBoundsMin.x = x3; - else if (x3 > meshBoundsMax.x) - meshBoundsMax.x = x3; - if (x4 < meshBoundsMin.x) - meshBoundsMin.x = x4; - else if (x4 > meshBoundsMax.x) - meshBoundsMax.x = x4; - - // Calculate min/max Y - if (y1 < meshBoundsMin.y) - meshBoundsMin.y = y1; - else if (y1 > meshBoundsMax.y) - meshBoundsMax.y = y1; - if (y2 < meshBoundsMin.y) - meshBoundsMin.y = y2; - else if (y2 > meshBoundsMax.y) - meshBoundsMax.y = y2; - if (y3 < meshBoundsMin.y) - meshBoundsMin.y = y3; - else if (y3 > meshBoundsMax.y) - meshBoundsMax.y = y3; - if (y4 < meshBoundsMin.y) - meshBoundsMin.y = y4; - else if (y4 > meshBoundsMax.y) - meshBoundsMax.y = y4; - - vertexIndex += 4; - } else { - if (!renderMeshes) - continue; - MeshAttachment meshAttachment = attachment as MeshAttachment; - if (meshAttachment != null) { - int meshVertexCount = meshAttachment.worldVerticesLength; - if (tempVertices.Length < meshVertexCount) - this.tempVertices = tempVertices = new float[meshVertexCount]; - meshAttachment.ComputeWorldVertices(slot, tempVertices); - - if (pmaVertexColors) { - color.a = (byte)(a * slot.a * meshAttachment.a); - color.r = (byte)(r * slot.r * meshAttachment.r * color.a); - color.g = (byte)(g * slot.g * meshAttachment.g * color.a); - color.b = (byte)(b * slot.b * meshAttachment.b * color.a); - if (slot.data.blendMode == BlendMode.additive) color.a = 0; - } else { - color.a = (byte)(a * slot.a * meshAttachment.a); - color.r = (byte)(r * slot.r * meshAttachment.r * 255); - color.g = (byte)(g * slot.g * meshAttachment.g * 255); - color.b = (byte)(b * slot.b * meshAttachment.b * 255); - } - - float[] meshUVs = meshAttachment.uvs; - float z = i * zSpacing; - for (int ii = 0; ii < meshVertexCount; ii += 2, vertexIndex++) { - float x = tempVertices[ii], y = tempVertices[ii + 1]; - vertices[vertexIndex].x = x; - vertices[vertexIndex].y = y; - vertices[vertexIndex].z = z; - colors[vertexIndex] = color; - uvs[vertexIndex].x = meshUVs[ii]; - uvs[vertexIndex].y = meshUVs[ii + 1]; - - if (x < meshBoundsMin.x) - meshBoundsMin.x = x; - else if (x > meshBoundsMax.x) - meshBoundsMax.x = x; - if (y < meshBoundsMin.y) - meshBoundsMin.y = y; - else if (y > meshBoundsMax.y) - meshBoundsMax.y = y; - } - } - } - } while (++i < drawOrderCount); } + int vertexIndex = 0; + ArraysMeshGenerator.FillVerts(skeleton, 0, drawOrderCount, this.zSpacing, pmaVertexColors, this.vertices, this.uvs, this.colors, ref vertexIndex, ref tempVertices, ref meshBoundsMin, ref meshBoundsMax, renderMeshes); - // Step 3. Move the mesh data into a UnityEngine.Mesh + + // Step 3. Move the mesh data into a UnityEngine.Mesh ============================================================ var currentSmartMesh = doubleBufferedMesh.GetNext(); // Double-buffer for performance. var currentMesh = currentSmartMesh.mesh; - - currentMesh.vertices = vertices; + currentMesh.vertices = this.vertices; currentMesh.colors32 = colors; currentMesh.uv = uvs; - - Vector3 meshBoundsExtents = meshBoundsMax - meshBoundsMin; - Vector3 meshBoundsCenter = meshBoundsMin + meshBoundsExtents * 0.5f; - currentMesh.bounds = new Bounds(meshBoundsCenter, meshBoundsExtents); + currentMesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax); var currentSmartMeshInstructionUsed = currentSmartMesh.instructionUsed; #if SPINE_OPTIONAL_NORMALS - if (currentSmartMeshInstructionUsed.vertexCount < vertexCount) { - if (calculateNormals) - currentMesh.normals = normals; - - // For dynamic calculated tangents, this needs to be moved out of the vertexCount check block when replacing the logic, also ensuring the size. - if (calculateTangents) - currentMesh.tangents = this.tangents; - } + if (calculateNormals && currentSmartMeshInstructionUsed.vertexCount < vertexCount) + currentMesh.normals = normals; #endif // Check if the triangles should also be updated. // This thorough structure check is cheaper than updating triangles every frame. bool mustUpdateMeshStructure = CheckIfMustUpdateMeshStructure(workingInstruction, currentSmartMeshInstructionUsed); + int submeshCount = workingSubmeshInstructions.Count; if (mustUpdateMeshStructure) { var thisSubmeshMaterials = this.submeshMaterials; thisSubmeshMaterials.Clear(false); - int submeshCount = workingSubmeshInstructions.Count; int oldSubmeshCount = submeshes.Count; submeshes.Capacity = submeshCount; for (int i = oldSubmeshCount; i < submeshCount; i++) - submeshes.Items[i] = new SubmeshTriangleBuffer(); + submeshes.Items[i] = new ArraysMeshGenerator.SubmeshTriangleBuffer(workingSubmeshInstructions.Items[i].triangleCount); var mutableTriangles = !workingInstruction.immutableTriangles; for (int i = 0, last = submeshCount - 1; i < submeshCount; i++) { var submeshInstruction = workingSubmeshInstructions.Items[i]; - if (mutableTriangles || i >= oldSubmeshCount) - SetSubmesh(i, submeshInstruction, - #if SPINE_OPTIONAL_FRONTFACING - currentInstructions.attachmentFlips, - #endif - i == last); + + if (mutableTriangles || i >= oldSubmeshCount) { + + #if !SPINE_OPTIONAL_FRONTFACING + var currentSubmesh = submeshes.Items[i]; + int instructionTriangleCount = submeshInstruction.triangleCount; + if (renderMeshes) { + ArraysMeshGenerator.FillTriangles(ref currentSubmesh.triangles, skeleton, instructionTriangleCount, submeshInstruction.firstVertexIndex, submeshInstruction.startSlot, submeshInstruction.endSlot, (i == last)); + currentSubmesh.triangleCount = instructionTriangleCount; + } else { + ArraysMeshGenerator.FillTrianglesQuads(ref currentSubmesh.triangles, ref currentSubmesh.triangleCount, ref currentSubmesh.firstVertex, submeshInstruction.firstVertexIndex, instructionTriangleCount, (i == last)); + } + #else + SetSubmesh(i, submeshInstruction, currentInstructions.attachmentFlips, i == last); + #endif + + } + thisSubmeshMaterials.Add(submeshInstruction.material); } @@ -635,12 +476,24 @@ namespace Spine.Unity { currentMesh.SetTriangles(submeshes.Items[i].triangles, i); } + #if SPINE_OPTIONAL_SOLVETANGENTS + if (calculateTangents) { + ArraysMeshGenerator.SolveTangents2DEnsureSize(ref this.tangents, ref this.tempTanBuffer, vertices.Length); + for (int i = 0; i < submeshCount; i++) { + var submesh = submeshes.Items[i]; + ArraysMeshGenerator.SolveTangents2DTriangles(this.tempTanBuffer, submesh.triangles, submesh.triangleCount, this.vertices, this.uvs, vertexCount); + } + ArraysMeshGenerator.SolveTangents2DBuffer(this.tangents, this.tempTanBuffer, vertexCount); + currentMesh.tangents = this.tangents; + } + #endif + // CheckIfMustUpdateMaterialArray (last pushed materials vs currently parsed materials) // Needs to check against the Working Submesh Instructions Materials instead of the cached submeshMaterials. { var lastPushedMaterials = this.sharedMaterials; bool mustUpdateRendererMaterials = mustUpdateMeshStructure || - (lastPushedMaterials.Length != workingSubmeshInstructions.Count); + (lastPushedMaterials.Length != submeshCount); if (!mustUpdateRendererMaterials) { var workingSubmeshInstructionsItems = workingSubmeshInstructions.Items; @@ -662,14 +515,12 @@ namespace Spine.Unity { } } - // Step 4. The UnityEngine.Mesh is ready. Set it as the MeshFilter's mesh. Store the instructions used for that mesh. + + // Step 4. The UnityEngine.Mesh is ready. Set it as the MeshFilter's mesh. Store the instructions used for that mesh. ============================================================ meshFilter.sharedMesh = currentMesh; currentSmartMesh.instructionUsed.Set(workingInstruction); - // Step 5. Miscellaneous - // Add stuff here if you want - #if SPINE_OPTIONAL_SUBMESHRENDERER if (submeshRenderers.Length > 0) { for (int i = 0; i < submeshRenderers.Length; i++) { @@ -748,10 +599,7 @@ namespace Spine.Unity { #if SPINE_OPTIONAL_FRONTFACING void SetSubmesh (int submeshIndex, Spine.Unity.MeshGeneration.SubmeshInstruction submeshInstructions, ExposedList flipStates, bool isLastSubmesh) { - #else - void SetSubmesh (int submeshIndex, Spine.Unity.MeshGeneration.SubmeshInstruction submeshInstructions, bool isLastSubmesh) { - #endif - SubmeshTriangleBuffer currentSubmesh = submeshes.Items[submeshIndex]; + var currentSubmesh = submeshes.Items[submeshIndex]; int[] triangles = currentSubmesh.triangles; int triangleCount = submeshInstructions.triangleCount; @@ -770,12 +618,8 @@ namespace Spine.Unity { currentSubmesh.triangles = triangles = new int[triangleCount]; currentSubmesh.triangleCount = 0; } - - #if SPINE_OPTIONAL_FRONTFACING + if (!this.renderMeshes && !this.frontFacing) { - #else - if (!this.renderMeshes) { - #endif // Use stored triangles if possible. if (currentSubmesh.firstVertex != firstVertex || currentSubmesh.triangleCount < triangleCount) { //|| currentSubmesh.triangleCount == 0 currentSubmesh.triangleCount = triangleCount; @@ -792,20 +636,14 @@ namespace Spine.Unity { } return; } - - // This method caches several .Items arrays. - // Never mutate their overlying ExposedList objects. - - #if SPINE_OPTIONAL_FRONTFACING + var flipStatesItems = flipStates.Items; - #endif // Iterate through all slots and store their triangles. var drawOrderItems = skeleton.DrawOrder.Items; int triangleIndex = 0; // Modified by loop for (int i = submeshInstructions.startSlot, n = submeshInstructions.endSlot; i < n; i++) { Attachment attachment = drawOrderItems[i].attachment; - #if SPINE_OPTIONAL_FRONTFACING bool flip = frontFacing && flipStatesItems[i]; // Add RegionAttachment triangles @@ -830,20 +668,6 @@ namespace Spine.Unity { firstVertex += 4; continue; } - #else - if (attachment is RegionAttachment) { - triangles[triangleIndex] = firstVertex; - triangles[triangleIndex + 1] = firstVertex + 2; - triangles[triangleIndex + 2] = firstVertex + 1; - triangles[triangleIndex + 3] = firstVertex + 2; - triangles[triangleIndex + 4] = firstVertex + 3; - triangles[triangleIndex + 5] = firstVertex + 1; - - triangleIndex += 6; - firstVertex += 4; - continue; - } - #endif // Add (Weighted)MeshAttachment triangles int[] attachmentTriangles; @@ -856,7 +680,6 @@ namespace Spine.Unity { continue; } - #if SPINE_OPTIONAL_FRONTFACING if (flip) { for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii += 3, triangleIndex += 3) { triangles[triangleIndex + 2] = firstVertex + attachmentTriangles[ii]; @@ -868,14 +691,11 @@ namespace Spine.Unity { triangles[triangleIndex] = firstVertex + attachmentTriangles[ii]; } } - #else - for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, triangleIndex++) - triangles[triangleIndex] = firstVertex + attachmentTriangles[ii]; - #endif firstVertex += attachmentVertexCount; } } + #endif #if UNITY_EDITOR void OnDrawGizmos () { @@ -943,13 +763,5 @@ namespace Spine.Unity { } } } - - class SubmeshTriangleBuffer { - public int[] triangles = new int[0]; - - // These two fields are used when renderMeshes == false - public int triangleCount; - public int firstVertex = -1; - } } }