[unity] Major render refactoring. Add clipping. See SpineMesh.cs

This commit is contained in:
pharan 2017-04-30 21:41:45 +08:00
parent c7cb29c779
commit e185e7f310
14 changed files with 1270 additions and 1906 deletions

View File

@ -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

View File

@ -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
}
/// <summary>Ensures the sizes of the passed array references. If they are not the correct size, a new array will be assigned to the references.</summary>
/// <returns><c>true</c>, if a resize occurred, <c>false</c> otherwise.</returns>
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<SubmeshTriangleBuffer> 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++;
}
}
}
}
}
/// <summary>Fills Unity vertex data buffers with verts from the Spine Skeleton.</summary>
/// <param name="skeleton">Spine.Skeleton source of the drawOrder array</param>
/// <param name="startSlot">Slot index of the first slot.</param>
/// <param name="endSlot">The index bounding the slot list. [endSlot - 1] is the last slot to be added.</param>
/// <param name="zSpacing">Spacing along the z-axis between attachments.</param>
/// <param name="pmaColors">If set to <c>true</c>, vertex colors will be premultiplied. This will also enable additive.</param>
/// <param name="verts">Vertex positions array. </param>
/// <param name="uvs">Vertex UV array.</param>
/// <param name="colors">Vertex color array (Color32).</param>
/// <param name="vertexIndex">A reference to the running vertex index. This is used when more than one submesh is to be added.</param>
/// <param name="tempVertBuffer">A temporary vertex position buffer for attachment position values.</param>
/// <param name="boundsMin">Reference to the running calculated minimum bounds.</param>
/// <param name="boundsMax">Reference to the running calculated maximum bounds.</param>
/// <param name = "renderMeshes">Include MeshAttachments. If false, it will ignore MeshAttachments.</param>
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;
}
/// <summary>Fills a submesh triangle buffer array.</summary>
/// <param name="skeleton">Spine.Skeleton source of draw order slots.</param>
/// <param name="triangleCount">The target triangle count.</param>
/// <param name="firstVertex">First vertex of this submesh.</param>
/// <param name="startSlot">Start slot.</param>
/// <param name="endSlot">End slot.</param>
/// <param name="triangleBuffer">The triangle buffer array to be filled. This reference will be replaced in case the triangle values don't fit.</param>
/// <param name="isLastSubmesh">If set to <c>true</c>, the triangle buffer is allowed to be larger than needed.</param>
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;
}
}
}
/// <summary>Creates a UnityEngine.Bounds struct from minimum and maximum value vectors.</summary>
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
/// <summary>Step 1 of solving tangents. Ensure you have buffers of the correct size.</summary>
/// <param name="tangentBuffer">Eventual Vector4[] tangent buffer to assign to Mesh.tangents.</param>
/// <param name="tempTanBuffer">Temporary Vector2 buffer for calculating directions.</param>
/// <param name="vertexCount">Number of vertices that require tangents (or the size of the vertex array)</param>
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.
}
/// <summary>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.</summary>
/// <param name="tempTanBuffer">A temporary Vector3[] for calculating tangents.</param>
/// <param name="vertices">The mesh's current vertex position buffer.</param>
/// <param name="triangles">The mesh's current triangles buffer.</param>
/// <param name="uvs">The mesh's current uvs buffer.</param>
/// <param name="vertexCount">Number of vertices that require tangents (or the size of the vertex array)</param>
/// <param name = "triangleCount">The number of triangle indexes in the triangle array to be used.</param>
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;
}
}
/// <summary>Step 3 of solving tangents. Fills a Vector4[] tangents array according to values calculated in step 2.</summary>
/// <param name="tangents">A Vector4[] that will eventually be used to set Mesh.tangents</param>
/// <param name="tempTanBuffer">A temporary Vector3[] for calculating tangents.</param>
/// <param name="vertexCount">Number of vertices that require tangents (or the size of the vertex array)</param>
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
}
}

View File

@ -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;
}
}
}

View File

@ -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<SmartMesh> doubleBufferedSmartMesh = new DoubleBuffered<SmartMesh>();
readonly ExposedList<SubmeshInstruction> currentInstructions = new ExposedList<SubmeshInstruction>();
readonly ExposedList<Attachment> attachmentBuffer = new ExposedList<Attachment>();
readonly ExposedList<SubmeshTriangleBuffer> submeshBuffers = new ExposedList<SubmeshTriangleBuffer>();
Material[] sharedMaterials = new Material[0];
/// <summary>
/// Generates a mesh based on a subset of instructions.
/// </summary>
/// <returns>A UnityEngine.Mesh.</returns>
/// <param name="instructions">A list of SubmeshInstructions.</param>
/// <param name="startSubmesh">The index of the starting submesh.</param>
/// <param name="endSubmesh">The exclusive upper bound of the last submesh to be included.</param>
public MeshAndMaterials GenerateMesh (ExposedList<SubmeshInstruction> 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<Attachment> attachmentsUsed = new ExposedList<Attachment>();
readonly ExposedList<SubmeshInstruction> instructionsUsed = new ExposedList<SubmeshInstruction>();
public void Set (Vector3[] verts, Vector2[] uvs, Color32[] colors, ExposedList<Attachment> attachments, ExposedList<SubmeshInstruction> 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<Attachment> attachments, ExposedList<SubmeshInstruction> 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
}
}

View File

@ -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 {
/// <summary>
/// Arrays submeshed mesh generator.
/// </summary>
public class ArraysSubmeshedMeshGenerator : ArraysMeshGenerator, ISubmeshedMeshGenerator, System.IDisposable {
readonly List<Slot> separators = new List<Slot>();
public List<Slot> Separators { get { return this.separators; } }
#region Settings
public float ZSpacing { get; set; }
#endregion
readonly DoubleBuffered<SmartMesh> doubleBufferedSmartMesh = new DoubleBuffered<SmartMesh>();
readonly SubmeshedMeshInstruction currentInstructions = new SubmeshedMeshInstruction();
readonly ExposedList<SubmeshTriangleBuffer> submeshBuffers = new ExposedList<SubmeshTriangleBuffer>();
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
/// <summary>Generates a mesh based on SubmeshedMeshInstructions</summary>
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<Attachment> attachmentsUsed = new ExposedList<Attachment>();
readonly ExposedList<SubmeshInstruction> instructionsUsed = new ExposedList<SubmeshInstruction>();
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
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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<Slot> 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<SubmeshInstruction> 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; }
}
/// <summary>Primarily a collection of Submesh Instructions. This constitutes instructions for how to construct a mesh containing submeshes.</summary>
public class SubmeshedMeshInstruction {
public readonly ExposedList<SubmeshInstruction> submeshInstructions = new ExposedList<SubmeshInstruction>();
public readonly ExposedList<Attachment> attachmentList = new ExposedList<Attachment>();
public int vertexCount = -1;
/// <summary>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.</summary>
public Material[] GetUpdatedMaterialArray (Material[] materials) {
return submeshInstructions.GetUpdatedMaterialArray(materials);
}
}
/// <summary>Instructions for how to generate a mesh or submesh out of a range of slots in a given skeleton.</summary>
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;
/// <summary>The number of slots in this SubmeshInstruction's range. Not necessarily the number of attachments.</summary>
public int SlotCount { get { return endSlot - startSlot; } }
}
public static class SubmeshInstructionExtensions {
/// <summary>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.</summary>
public static Material[] GetUpdatedMaterialArray (this ExposedList<SubmeshInstruction> 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;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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<MeshFilter>().sharedMesh;
mesh.RecalculateBounds();
var bounds = mesh.bounds;

View File

@ -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<Spine.Unity.MeshRendererBuffers.SmartMesh> 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<MeshRendererBuffers.SmartMesh>();
// 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
}

View File

@ -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<MeshFilter>();
meshRenderer = GetComponent<MeshRenderer>();
if (buffers == null) {
buffers = new MeshRendererBuffers();
buffers.Initialize();
if (meshGenerator != null) return;
meshGenerator = new MeshGenerator();
meshFilter = GetComponent<MeshFilter>();
meshRenderer = GetComponent<MeshRenderer>();
currentInstructions.Clear();
}
}
public void ClearMesh () {
@ -75,9 +84,31 @@ namespace Spine.Unity.Modules {
public void RenderParts (ExposedList<SubmeshInstruction> 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) {

View File

@ -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);

View File

@ -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 {
/// <summary>Renders a skeleton.</summary>
@ -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<Slot> separatorSlots = new List<Slot>();
[UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators")] [SpineSlot] public string[] separatorSlotNames = new string[0];
[System.NonSerialized] public readonly List<Slot> separatorSlots = new List<Slot>();
public float zSpacing;
public bool renderMeshes = true, immutableTriangles;
[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<SkeletonRenderer.SmartMesh> doubleBufferedMesh;
readonly SmartMesh.Instruction currentInstructions = new SmartMesh.Instruction();
readonly ExposedList<ArraysMeshGenerator.SubmeshTriangleBuffer> submeshes = new ExposedList<ArraysMeshGenerator.SubmeshTriangleBuffer>();
readonly ExposedList<Material> submeshMaterials = new ExposedList<Material>();
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<T> (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<MeshFilter>();
meshRenderer = GetComponent<MeshRenderer>();
doubleBufferedMesh = new DoubleBuffered<SmartMesh>();
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<Slot> 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;
}
///<summary>This is a Mesh that also stores the instructions SkeletonRenderer generated for it.</summary>
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<Attachment> attachments = new ExposedList<Attachment>();
public readonly ExposedList<Spine.Unity.MeshGeneration.SubmeshInstruction> submeshInstructions = new ExposedList<Spine.Unity.MeshGeneration.SubmeshInstruction>();
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);
}
}
}