/******************************************************************************
* Spine Runtimes Software License
* Version 2.1
*
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to install, execute and perform the Spine Runtimes
* Software (the "Software") solely for internal use. Without the written
* permission of Esoteric Software (typically granted by licensing Spine), you
* may not (a) modify, translate, adapt or otherwise create derivative works,
* improvements of the Software or develop new applications using the Software
* 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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) 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 System;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using Spine;
/// Renders a skeleton.
[ExecuteInEditMode, RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class SkeletonRenderer : MonoBehaviour
{
public delegate void SkeletonRendererDelegate(SkeletonRenderer skeletonRenderer);
public SkeletonRendererDelegate OnReset;
[System.NonSerialized]
public bool valid;
[System.NonSerialized]
public Skeleton skeleton;
public SkeletonDataAsset skeletonDataAsset;
public String initialSkinName;
public bool calculateNormals, calculateTangents;
public float zSpacing;
public bool renderMeshes = true, immutableTriangles;
public bool frontFacing;
public bool logErrors = false;
[SpineSlot]
public string[] submeshSeparators = new string[0];
[HideInInspector]
public List submeshSeparatorSlots = new List();
private MeshRenderer meshRenderer;
private MeshFilter meshFilter;
private Mesh mesh1, mesh2;
private bool useMesh1;
private float[] tempVertices = new float[8];
private int lastVertexCount;
private Vector3[] vertices;
private Color32[] colors;
private Vector2[] uvs;
private Material[] sharedMaterials = new Material[0];
private readonly List submeshMaterials = new List();
private readonly List submeshes = new List();
private SkeletonUtilitySubmeshRenderer[] submeshRenderers;
public virtual void Reset()
{
if (meshFilter != null)
meshFilter.sharedMesh = null;
meshRenderer = GetComponent();
if (meshRenderer != null) meshRenderer.sharedMaterial = null;
if (mesh1 != null)
{
if (Application.isPlaying)
Destroy(mesh1);
else
DestroyImmediate(mesh1);
}
if (mesh2 != null)
{
if (Application.isPlaying)
Destroy(mesh2);
else
DestroyImmediate(mesh2);
}
mesh1 = null;
mesh2 = null;
lastVertexCount = 0;
vertices = null;
colors = null;
uvs = null;
sharedMaterials = new Material[0];
submeshMaterials.Clear();
submeshes.Clear();
skeleton = null;
valid = false;
if (!skeletonDataAsset)
{
if (logErrors)
Debug.LogError("Missing SkeletonData asset.", this);
return;
}
SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(false);
if (skeletonData == null)
return;
valid = true;
meshFilter = GetComponent();
mesh1 = newMesh();
mesh2 = newMesh();
vertices = new Vector3[0];
skeleton = new Skeleton(skeletonData);
if (initialSkinName != null && initialSkinName.Length > 0 && initialSkinName != "default")
skeleton.SetSkin(initialSkinName);
submeshSeparatorSlots.Clear();
for (int i = 0; i < submeshSeparators.Length; i++)
{
submeshSeparatorSlots.Add(skeleton.FindSlot(submeshSeparators[i]));
}
CollectSubmeshRenderers();
LateUpdate();
if (OnReset != null)
OnReset(this);
}
public void CollectSubmeshRenderers()
{
submeshRenderers = GetComponentsInChildren();
}
public virtual void Awake()
{
Reset();
}
public virtual void OnDestroy()
{
if (mesh1 != null)
{
if (Application.isPlaying)
Destroy(mesh1);
else
DestroyImmediate(mesh1);
}
if (mesh2 != null)
{
if (Application.isPlaying)
Destroy(mesh2);
else
DestroyImmediate(mesh2);
}
mesh1 = null;
mesh2 = null;
}
private Mesh newMesh()
{
Mesh mesh = new Mesh();
mesh.name = "Skeleton Mesh";
mesh.hideFlags = HideFlags.HideAndDontSave;
mesh.MarkDynamic();
return mesh;
}
public virtual void LateUpdate()
{
if (!valid)
return;
// Count vertices and submesh triangles.
int vertexCount = 0;
int submeshTriangleCount = 0, submeshFirstVertex = 0, submeshStartSlotIndex = 0;
Material lastMaterial = null;
submeshMaterials.Clear();
List drawOrder = skeleton.DrawOrder;
int drawOrderCount = drawOrder.Count;
bool renderMeshes = this.renderMeshes;
for (int i = 0; i < drawOrderCount; i++)
{
Slot slot = drawOrder[i];
Attachment attachment = slot.attachment;
object rendererObject;
int attachmentVertexCount, attachmentTriangleCount;
if (attachment is RegionAttachment)
{
rendererObject = ((RegionAttachment)attachment).RendererObject;
attachmentVertexCount = 4;
attachmentTriangleCount = 6;
}
else
{
if (!renderMeshes)
continue;
if (attachment is MeshAttachment)
{
MeshAttachment meshAttachment = (MeshAttachment)attachment;
rendererObject = meshAttachment.RendererObject;
attachmentVertexCount = meshAttachment.vertices.Length >> 1;
attachmentTriangleCount = meshAttachment.triangles.Length;
}
else if (attachment is SkinnedMeshAttachment)
{
SkinnedMeshAttachment meshAttachment = (SkinnedMeshAttachment)attachment;
rendererObject = meshAttachment.RendererObject;
attachmentVertexCount = meshAttachment.uvs.Length >> 1;
attachmentTriangleCount = meshAttachment.triangles.Length;
}
else
continue;
}
// Populate submesh when material changes.
#if !SPINE_TK2D
Material material = (Material)((AtlasRegion)rendererObject).page.rendererObject;
#else
Material material = (rendererObject.GetType() == typeof(Material)) ? (Material)rendererObject : (Material)((AtlasRegion)rendererObject).page.rendererObject;
#endif
if ((lastMaterial != material && lastMaterial != null) || submeshSeparatorSlots.Contains(slot))
{
AddSubmesh(lastMaterial, submeshStartSlotIndex, i, submeshTriangleCount, submeshFirstVertex, false);
submeshTriangleCount = 0;
submeshFirstVertex = vertexCount;
submeshStartSlotIndex = i;
}
lastMaterial = material;
submeshTriangleCount += attachmentTriangleCount;
vertexCount += attachmentVertexCount;
}
AddSubmesh(lastMaterial, submeshStartSlotIndex, drawOrderCount, submeshTriangleCount, submeshFirstVertex, true);
// Set materials.
if (submeshMaterials.Count == sharedMaterials.Length)
submeshMaterials.CopyTo(sharedMaterials);
else
sharedMaterials = submeshMaterials.ToArray();
meshRenderer.sharedMaterials = sharedMaterials;
// Ensure mesh data is the right size.
Vector3[] vertices = this.vertices;
bool newTriangles = vertexCount > vertices.Length;
if (newTriangles)
{
// Not enough vertices, increase size.
this.vertices = vertices = new Vector3[vertexCount];
this.colors = new Color32[vertexCount];
this.uvs = new Vector2[vertexCount];
mesh1.Clear();
mesh2.Clear();
}
else
{
// Too many vertices, zero the extra.
Vector3 zero = Vector3.zero;
for (int i = vertexCount, n = lastVertexCount; i < n; i++)
vertices[i] = zero;
}
lastVertexCount = vertexCount;
// Setup mesh.
float[] tempVertices = this.tempVertices;
Vector2[] uvs = this.uvs;
Color32[] colors = this.colors;
int vertexIndex = 0;
Color32 color = new Color32();
float zSpacing = this.zSpacing;
float a = skeleton.a * 255, r = skeleton.r, g = skeleton.g, b = skeleton.b;
for (int i = 0; i < drawOrderCount; i++)
{
Slot slot = drawOrder[i];
Attachment attachment = slot.attachment;
if (attachment is RegionAttachment)
{
RegionAttachment regionAttachment = (RegionAttachment)attachment;
regionAttachment.ComputeWorldVertices(slot.bone, tempVertices);
float z = i * zSpacing;
vertices[vertexIndex] = new Vector3(tempVertices[RegionAttachment.X1], tempVertices[RegionAttachment.Y1], z);
vertices[vertexIndex + 1] = new Vector3(tempVertices[RegionAttachment.X4], tempVertices[RegionAttachment.Y4], z);
vertices[vertexIndex + 2] = new Vector3(tempVertices[RegionAttachment.X2], tempVertices[RegionAttachment.Y2], z);
vertices[vertexIndex + 3] = new Vector3(tempVertices[RegionAttachment.X3], tempVertices[RegionAttachment.Y3], z);
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.additiveBlending)
color.a = 0;
colors[vertexIndex] = color;
colors[vertexIndex + 1] = color;
colors[vertexIndex + 2] = color;
colors[vertexIndex + 3] = color;
float[] regionUVs = regionAttachment.uvs;
uvs[vertexIndex] = new Vector2(regionUVs[RegionAttachment.X1], regionUVs[RegionAttachment.Y1]);
uvs[vertexIndex + 1] = new Vector2(regionUVs[RegionAttachment.X4], regionUVs[RegionAttachment.Y4]);
uvs[vertexIndex + 2] = new Vector2(regionUVs[RegionAttachment.X2], regionUVs[RegionAttachment.Y2]);
uvs[vertexIndex + 3] = new Vector2(regionUVs[RegionAttachment.X3], regionUVs[RegionAttachment.Y3]);
vertexIndex += 4;
}
else
{
if (!renderMeshes)
continue;
if (attachment is MeshAttachment)
{
MeshAttachment meshAttachment = (MeshAttachment)attachment;
int meshVertexCount = meshAttachment.vertices.Length;
if (tempVertices.Length < meshVertexCount)
this.tempVertices = tempVertices = new float[meshVertexCount];
meshAttachment.ComputeWorldVertices(slot, tempVertices);
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.additiveBlending)
color.a = 0;
float[] meshUVs = meshAttachment.uvs;
float z = i * zSpacing;
for (int ii = 0; ii < meshVertexCount; ii += 2, vertexIndex++)
{
vertices[vertexIndex] = new Vector3(tempVertices[ii], tempVertices[ii + 1], z);
colors[vertexIndex] = color;
uvs[vertexIndex] = new Vector2(meshUVs[ii], meshUVs[ii + 1]);
}
}
else if (attachment is SkinnedMeshAttachment)
{
SkinnedMeshAttachment meshAttachment = (SkinnedMeshAttachment)attachment;
int meshVertexCount = meshAttachment.uvs.Length;
if (tempVertices.Length < meshVertexCount)
this.tempVertices = tempVertices = new float[meshVertexCount];
meshAttachment.ComputeWorldVertices(slot, tempVertices);
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.additiveBlending)
color.a = 0;
float[] meshUVs = meshAttachment.uvs;
float z = i * zSpacing;
for (int ii = 0; ii < meshVertexCount; ii += 2, vertexIndex++)
{
vertices[vertexIndex] = new Vector3(tempVertices[ii], tempVertices[ii + 1], z);
colors[vertexIndex] = color;
uvs[vertexIndex] = new Vector2(meshUVs[ii], meshUVs[ii + 1]);
}
}
}
}
// Double buffer mesh.
Mesh mesh = useMesh1 ? mesh1 : mesh2;
meshFilter.sharedMesh = mesh;
mesh.vertices = vertices;
mesh.colors32 = colors;
mesh.uv = uvs;
int submeshCount = submeshMaterials.Count;
mesh.subMeshCount = submeshCount;
for (int i = 0; i < submeshCount; ++i)
mesh.SetTriangles(submeshes[i].triangles, i);
mesh.RecalculateBounds();
if (newTriangles && calculateNormals)
{
Vector3[] normals = new Vector3[vertexCount];
Vector3 normal = new Vector3(0, 0, -1);
for (int i = 0; i < vertexCount; i++)
normals[i] = normal;
(useMesh1 ? mesh2 : mesh1).vertices = vertices; // Set other mesh vertices.
mesh1.normals = normals;
mesh2.normals = normals;
if (calculateTangents)
{
Vector4[] tangents = new Vector4[vertexCount];
Vector3 tangent = new Vector3(0, 0, 1);
for (int i = 0; i < vertexCount; i++)
tangents[i] = tangent;
mesh1.tangents = tangents;
mesh2.tangents = tangents;
}
}
if (submeshRenderers.Length > 0)
{
foreach (var submeshRenderer in submeshRenderers)
{
if (submeshRenderer.submeshIndex < sharedMaterials.Length)
submeshRenderer.SetMesh(meshRenderer, useMesh1 ? mesh1 : mesh2, sharedMaterials[submeshRenderer.submeshIndex]);
else
submeshRenderer.GetComponent().enabled = false;
}
}
useMesh1 = !useMesh1;
}
/** Stores vertices and triangles for a single material. */
private void AddSubmesh(Material material, int startSlot, int endSlot, int triangleCount, int firstVertex, bool lastSubmesh)
{
int submeshIndex = submeshMaterials.Count;
submeshMaterials.Add(material);
if (submeshes.Count <= submeshIndex)
submeshes.Add(new Submesh());
else if (immutableTriangles)
return;
Submesh submesh = submeshes[submeshIndex];
int[] triangles = submesh.triangles;
int trianglesCapacity = triangles.Length;
if (lastSubmesh && trianglesCapacity > triangleCount)
{
// Last submesh may have more triangles than required, so zero triangles to the end.
for (int i = triangleCount; i < trianglesCapacity; i++)
triangles[i] = 0;
submesh.triangleCount = triangleCount;
}
else if (trianglesCapacity != triangleCount)
{
// Reallocate triangles when not the exact size needed.
submesh.triangles = triangles = new int[triangleCount];
submesh.triangleCount = 0;
}
if (!renderMeshes && !frontFacing)
{
// Use stored triangles if possible.
if (submesh.firstVertex != firstVertex || submesh.triangleCount < triangleCount)
{
submesh.triangleCount = triangleCount;
submesh.firstVertex = firstVertex;
int drawOrderIndex = 0;
for (int i = 0; i < triangleCount; i += 6, firstVertex += 4, drawOrderIndex++)
{
triangles[i] = firstVertex;
triangles[i + 1] = firstVertex + 2;
triangles[i + 2] = firstVertex + 1;
triangles[i + 3] = firstVertex + 2;
triangles[i + 4] = firstVertex + 3;
triangles[i + 5] = firstVertex + 1;
}
}
return;
}
// Store triangles.
List drawOrder = skeleton.DrawOrder;
for (int i = startSlot, triangleIndex = 0; i < endSlot; i++)
{
Slot slot = drawOrder[i];
Attachment attachment = slot.attachment;
Bone bone = slot.bone;
bool flip = frontFacing && ((bone.WorldFlipX != bone.WorldFlipY) != (Mathf.Sign(bone.WorldScaleX) != Mathf.Sign(bone.WorldScaleY)));
if (attachment is RegionAttachment)
{
if (!flip)
{
triangles[triangleIndex] = firstVertex;
triangles[triangleIndex + 1] = firstVertex + 2;
triangles[triangleIndex + 2] = firstVertex + 1;
triangles[triangleIndex + 3] = firstVertex + 2;
triangles[triangleIndex + 4] = firstVertex + 3;
triangles[triangleIndex + 5] = firstVertex + 1;
}
else
{
triangles[triangleIndex] = firstVertex + 1;
triangles[triangleIndex + 1] = firstVertex + 2;
triangles[triangleIndex + 2] = firstVertex;
triangles[triangleIndex + 3] = firstVertex + 1;
triangles[triangleIndex + 4] = firstVertex + 3;
triangles[triangleIndex + 5] = firstVertex + 2;
}
triangleIndex += 6;
firstVertex += 4;
continue;
}
int[] attachmentTriangles;
int attachmentVertexCount;
if (attachment is MeshAttachment)
{
MeshAttachment meshAttachment = (MeshAttachment)attachment;
attachmentVertexCount = meshAttachment.vertices.Length >> 1;
attachmentTriangles = meshAttachment.triangles;
}
else if (attachment is SkinnedMeshAttachment)
{
SkinnedMeshAttachment meshAttachment = (SkinnedMeshAttachment)attachment;
attachmentVertexCount = meshAttachment.uvs.Length >> 1;
attachmentTriangles = meshAttachment.triangles;
}
else
continue;
if (flip)
{
for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii += 3, triangleIndex += 3)
{
triangles[triangleIndex + 2] = firstVertex + attachmentTriangles[ii];
triangles[triangleIndex + 1] = firstVertex + attachmentTriangles[ii + 1];
triangles[triangleIndex] = firstVertex + attachmentTriangles[ii + 2];
}
}
else
{
for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, triangleIndex++)
{
triangles[triangleIndex] = firstVertex + attachmentTriangles[ii];
}
}
firstVertex += attachmentVertexCount;
}
}
#if UNITY_EDITOR
void OnDrawGizmos()
{
// Make selection easier by drawing a clear gizmo over the skeleton.
if (vertices == null) return;
Vector3 gizmosCenter = new Vector3();
Vector3 gizmosSize = new Vector3();
Vector3 min = new Vector3(float.MaxValue, float.MaxValue, 0f);
Vector3 max = new Vector3(float.MinValue, float.MinValue, 0f);
foreach (Vector3 vert in vertices)
{
min = Vector3.Min(min, vert);
max = Vector3.Max(max, vert);
}
float width = max.x - min.x;
float height = max.y - min.y;
gizmosCenter = new Vector3(min.x + (width / 2f), min.y + (height / 2f), 0f);
gizmosSize = new Vector3(width, height, 1f);
Gizmos.color = Color.clear;
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.DrawCube(gizmosCenter, gizmosSize);
}
#endif
}
class Submesh
{
public int[] triangles = new int[0];
public int triangleCount;
public int firstVertex = -1;
}