mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-21 01:36:02 +08:00
398 lines
15 KiB
C#
398 lines
15 KiB
C#
/******************************************************************************
|
|
* 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;
|
|
|
|
/// <summary>Renders a skeleton.</summary>
|
|
[ExecuteInEditMode, RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
|
|
public class SkeletonRenderer : MonoBehaviour {
|
|
[System.NonSerialized]
|
|
public bool valid;
|
|
[System.NonSerialized]
|
|
public Skeleton skeleton;
|
|
|
|
public SkeletonDataAsset skeletonDataAsset;
|
|
public String initialSkinName;
|
|
public bool calculateNormals;
|
|
public bool calculateTangents;
|
|
public float zSpacing;
|
|
public bool renderMeshes;
|
|
|
|
private MeshFilter meshFilter;
|
|
private Mesh 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<Material> submeshMaterials = new List<Material>();
|
|
private readonly List<Submesh> submeshes = new List<Submesh>();
|
|
private readonly int[] quadTriangles = {0, 2, 1, 2, 3, 1};
|
|
|
|
public virtual void Reset () {
|
|
if (meshFilter != null) meshFilter.sharedMesh = null;
|
|
if (mesh != null) DestroyImmediate(mesh);
|
|
if (renderer != null) renderer.sharedMaterial = null;
|
|
mesh = null;
|
|
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) {
|
|
Debug.LogError("Missing SkeletonData asset.", this);
|
|
return;
|
|
}
|
|
SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(false);
|
|
if (skeletonData == null) return;
|
|
valid = true;
|
|
|
|
meshFilter = GetComponent<MeshFilter>();
|
|
mesh1 = newMesh();
|
|
mesh2 = newMesh();
|
|
vertices = new Vector3[0];
|
|
|
|
skeleton = new Skeleton(skeletonData);
|
|
if (initialSkinName != null && initialSkinName.Length > 0 && initialSkinName != "default")
|
|
skeleton.SetSkin(initialSkinName);
|
|
}
|
|
|
|
public void Awake () {
|
|
Reset();
|
|
}
|
|
|
|
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<Slot> drawOrder = skeleton.DrawOrder;
|
|
int drawOrderCount = drawOrder.Count;
|
|
bool renderMeshes = this.renderMeshes;
|
|
for (int i = 0; i < drawOrderCount; i++) {
|
|
Attachment attachment = drawOrder[i].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.
|
|
Material material = (Material)((AtlasRegion)rendererObject).page.rendererObject;
|
|
if (lastMaterial != material && lastMaterial != null) {
|
|
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();
|
|
renderer.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 x = skeleton.x, y = skeleton.y, 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(x, y, 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) tempVertices = new float[meshVertexCount];
|
|
meshAttachment.ComputeWorldVertices(x, y, 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) tempVertices = new float[meshVertexCount];
|
|
meshAttachment.ComputeWorldVertices(x, y, 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;
|
|
}
|
|
}
|
|
|
|
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());
|
|
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) {
|
|
// Use stored triangles if possible.
|
|
if (submesh.firstVertex != firstVertex || submesh.triangleCount < triangleCount) {
|
|
submesh.triangleCount = triangleCount;
|
|
submesh.firstVertex = firstVertex;
|
|
for (int i = 0; i < triangleCount; i += 6, firstVertex += 4) {
|
|
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<Slot> drawOrder = skeleton.DrawOrder;
|
|
for (int i = startSlot, triangleIndex = 0; i < endSlot; i++) {
|
|
Attachment attachment = drawOrder[i].attachment;
|
|
int[] attachmentTriangles;
|
|
int attachmentVertexCount;
|
|
if (attachment is RegionAttachment) {
|
|
attachmentVertexCount = 4;
|
|
attachmentTriangles = quadTriangles;
|
|
} else 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;
|
|
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;
|
|
}
|