mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-13 02:28:44 +08:00
[unity] Major render refactoring. Add clipping. See SpineMesh.cs
This commit is contained in:
parent
c7cb29c779
commit
e185e7f310
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -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
@ -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;
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user