/******************************************************************************
* 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_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 {
/// Renders a skeleton.
[ExecuteAlways, RequireComponent(typeof(MeshFilter), typeof(MeshRenderer)), DisallowMultipleComponent]
[HelpURL("http://esotericsoftware.com/spine-unity-documentation#Rendering")]
public class SkeletonRenderer : MonoBehaviour, ISkeletonComponent {
public delegate void SkeletonRendererDelegate (SkeletonRenderer skeletonRenderer);
public SkeletonRendererDelegate OnRebuild;
public SkeletonDataAsset skeletonDataAsset;
public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } } // ISkeletonComponent
public string initialSkinName;
#region Advanced
// Submesh Separation
[UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators")]
[SpineSlot]
public string[] separatorSlotNames = new string[0];
[System.NonSerialized]
public readonly List separatorSlots = new List();
public float zSpacing;
public bool renderMeshes = true, immutableTriangles;
public bool pmaVertexColors = true;
public bool clearStateOnDisable = false;
#if SPINE_OPTIONAL_NORMALS
public bool calculateNormals;
#endif
#if SPINE_OPTIONAL_SOLVETANGENTS
public bool calculateTangents;
#endif
#if SPINE_OPTIONAL_FRONTFACING
public bool frontFacing;
#endif
public bool logErrors = false;
#if SPINE_OPTIONAL_RENDEROVERRIDE
public bool disableRenderingOnOverride = true;
public delegate void InstructionDelegate (SkeletonRenderer.SmartMesh.Instruction instruction);
event InstructionDelegate generateMeshOverride;
public event InstructionDelegate GenerateMeshOverride {
add {
generateMeshOverride += value;
if (disableRenderingOnOverride && generateMeshOverride != null) {
Initialize(false);
meshRenderer.enabled = false;
}
}
remove {
generateMeshOverride -= value;
if (disableRenderingOnOverride && generateMeshOverride == null) {
Initialize(false);
meshRenderer.enabled = true;
}
}
}
#endif
#if SPINE_OPTIONAL_MATERIALOVERRIDE
[System.NonSerialized] readonly Dictionary customMaterialOverride = new Dictionary();
public Dictionary CustomMaterialOverride { get { return customMaterialOverride; } }
#endif
// Custom Slot Material
[System.NonSerialized] readonly Dictionary customSlotMaterials = new Dictionary();
public Dictionary CustomSlotMaterials { get { return customSlotMaterials; } }
#endregion
MeshRenderer meshRenderer;
MeshFilter meshFilter;
[System.NonSerialized] public bool valid;
[System.NonSerialized] public Skeleton skeleton;
public Skeleton Skeleton {
get {
Initialize(false);
return skeleton;
}
}
Spine.Unity.DoubleBuffered doubleBufferedMesh;
readonly SmartMesh.Instruction currentInstructions = new SmartMesh.Instruction();
readonly ExposedList submeshes = new ExposedList();
readonly ExposedList submeshMaterials = new ExposedList();
Material[] sharedMaterials = new Material[0];
float[] tempVertices = new float[8];
Vector3[] vertices;
Color32[] colors;
Vector2[] uvs;
#if SPINE_OPTIONAL_NORMALS
Vector3[] normals;
#endif
#if SPINE_OPTIONAL_SOLVETANGENTS
Vector4[] tangents;
Vector2[] tempTanBuffer;
#endif
#region Runtime Instantiation
public static T NewSpineGameObject (SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer {
return SkeletonRenderer.AddSpineComponent(new GameObject("New Spine GameObject"), skeletonDataAsset);
}
/// Add and prepare a Spine component that derives from SkeletonRenderer to a GameObject at runtime.
/// T should be SkeletonRenderer or any of its derived classes.
public static T AddSpineComponent (GameObject gameObject, SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer {
var c = gameObject.AddComponent();
if (skeletonDataAsset != null) {
c.skeletonDataAsset = skeletonDataAsset;
c.Initialize(false);
}
return c;
}
#endregion
public virtual void Awake () {
Initialize(false);
}
void OnDisable () {
if (clearStateOnDisable && valid)
ClearState();
}
protected virtual void ClearState () {
meshFilter.sharedMesh = null;
currentInstructions.Clear();
if (skeleton != null) skeleton.SetToSetupPose();
}
public virtual void Initialize (bool overwrite) {
if (valid && !overwrite)
return;
// Clear
{
if (meshFilter != null)
meshFilter.sharedMesh = null;
meshRenderer = GetComponent();
if (meshRenderer != null) meshRenderer.sharedMaterial = null;
currentInstructions.Clear();
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();
meshRenderer = GetComponent();
doubleBufferedMesh = new DoubleBuffered();
vertices = new Vector3[0];
skeleton = new Skeleton(skeletonData);
if (!string.IsNullOrEmpty(initialSkinName) && initialSkinName != "default")
skeleton.SetSkin(initialSkinName);
separatorSlots.Clear();
for (int i = 0; i < separatorSlotNames.Length; i++)
separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));
LateUpdate();
if (OnRebuild != null)
OnRebuild(this);
}
public virtual void LateUpdate () {
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 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;
#if SPINE_OPTIONAL_FRONTFACING
var workingFlips = workingInstruction.attachmentFlips;
workingFlips.Clear(false);
workingFlips.GrowIfNeeded(drawOrderCount);
workingFlips.Count = drawOrderCount;
var workingFlipsItems = workingFlips.Items;
#endif
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;
#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;
#if SPINE_OPTIONAL_FRONTFACING
bool flip = frontFacing && (slot.bone.WorldSignX != slot.bone.WorldSignY);
workingFlipsItems[i] = flip;
#endif
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 && this.generateMeshOverride != null) {
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;
#if SPINE_OPTIONAL_FRONTFACING
workingInstruction.frontFacing = this.frontFacing;
#endif
// 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;
}
}
}
#endif
#if SPINE_OPTIONAL_RENDEROVERRIDE
if (this.generateMeshOverride != null) {
this.generateMeshOverride(workingInstruction);
if (disableRenderingOnOverride) return;
}
#endif
// STEP 2. Update vertex buffer based on verts from the attachments. ============================================================
// Uses values that were also stored in workingInstruction.
bool vertexCountIncreased = ArraysMeshGenerator.EnsureSize(vertexCount, ref this.vertices, ref this.uvs, ref this.colors);
#if SPINE_OPTIONAL_NORMALS
if (vertexCountIncreased && calculateNormals) {
Vector3[] localNormals = this.normals = new Vector3[vertexCount];
Vector3 normal = new Vector3(0, 0, -1);
for (int i = 0; i < vertexCount; i++)
localNormals[i] = normal;
}
#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;
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 currentMesh = currentSmartMesh.mesh;
currentMesh.vertices = this.vertices;
currentMesh.colors32 = colors;
currentMesh.uv = uvs;
currentMesh.bounds = ArraysMeshGenerator.ToBounds(meshBoundsMin, meshBoundsMax);
var currentSmartMeshInstructionUsed = currentSmartMesh.instructionUsed;
#if SPINE_OPTIONAL_NORMALS
if (calculateNormals && currentSmartMeshInstructionUsed.vertexCount < vertexCount)
currentMesh.normals = normals;
#endif
// Check if the triangles should also be updated.
// This thorough structure check is cheaper than updating triangles every frame.
bool mustUpdateMeshStructure = CheckIfMustUpdateMeshStructure(workingInstruction, currentSmartMeshInstructionUsed);
int submeshCount = workingSubmeshInstructions.Count;
if (mustUpdateMeshStructure) {
var thisSubmeshMaterials = this.submeshMaterials;
thisSubmeshMaterials.Clear(false);
int 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) {
#if !SPINE_OPTIONAL_FRONTFACING
var currentSubmesh = submeshes.Items[i];
int instructionTriangleCount = submeshInstruction.triangleCount;
if (renderMeshes) {
ArraysMeshGenerator.FillTriangles(ref currentSubmesh.triangles, skeleton, instructionTriangleCount, submeshInstruction.firstVertexIndex, submeshInstruction.startSlot, submeshInstruction.endSlot, (i == last));
currentSubmesh.triangleCount = instructionTriangleCount;
} else {
ArraysMeshGenerator.FillTrianglesQuads(ref currentSubmesh.triangles, ref currentSubmesh.triangleCount, ref currentSubmesh.firstVertex, submeshInstruction.firstVertexIndex, instructionTriangleCount, (i == last));
}
#else
SetSubmesh(i, submeshInstruction, currentInstructions.attachmentFlips, i == last);
#endif
}
thisSubmeshMaterials.Add(submeshInstruction.material);
}
currentMesh.subMeshCount = submeshCount;
for (int i = 0; i < submeshCount; ++i)
currentMesh.SetTriangles(submeshes.Items[i].triangles, i);
}
#if SPINE_OPTIONAL_SOLVETANGENTS
if (calculateTangents) {
ArraysMeshGenerator.SolveTangents2DEnsureSize(ref this.tangents, ref this.tempTanBuffer, vertices.Length);
for (int i = 0; i < submeshCount; i++) {
var submesh = submeshes.Items[i];
ArraysMeshGenerator.SolveTangents2DTriangles(this.tempTanBuffer, submesh.triangles, submesh.triangleCount, this.vertices, this.uvs, vertexCount);
}
ArraysMeshGenerator.SolveTangents2DBuffer(this.tangents, this.tempTanBuffer, vertexCount);
currentMesh.tangents = this.tangents;
}
#endif
// CheckIfMustUpdateMaterialArray (last pushed materials vs currently parsed materials)
// Needs to check against the Working Submesh Instructions Materials instead of the cached submeshMaterials.
{
var lastPushedMaterials = this.sharedMaterials;
bool mustUpdateRendererMaterials = mustUpdateMeshStructure ||
(lastPushedMaterials.Length != 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;
}
#if SPINE_OPTIONAL_FRONTFACING
if (a.frontFacing != b.frontFacing) { // if settings changed
return true;
} else if (a.frontFacing) { // if settings matched, only need to check one.
var flipsA = a.attachmentFlips.Items;
var flipsB = b.attachmentFlips.Items;
for (int i = 0; i < attachmentCountB; i++) {
if (flipsA[i] != flipsB[i])
return true;
}
}
#endif
// 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;
}
#if SPINE_OPTIONAL_FRONTFACING
void SetSubmesh (int submeshIndex, Spine.Unity.MeshGeneration.SubmeshInstruction submeshInstructions, ExposedList flipStates, bool isLastSubmesh) {
var currentSubmesh = submeshes.Items[submeshIndex];
int[] triangles = currentSubmesh.triangles;
int triangleCount = submeshInstructions.triangleCount;
int firstVertex = submeshInstructions.firstVertexIndex;
int trianglesCapacity = triangles.Length;
if (isLastSubmesh && 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;
currentSubmesh.triangleCount = triangleCount;
} else if (trianglesCapacity != triangleCount) {
// Reallocate triangles when not the exact size needed.
currentSubmesh.triangles = triangles = new int[triangleCount];
currentSubmesh.triangleCount = 0;
}
if (!this.renderMeshes && !this.frontFacing) {
// Use stored triangles if possible.
if (currentSubmesh.firstVertex != firstVertex || currentSubmesh.triangleCount < triangleCount) { //|| currentSubmesh.triangleCount == 0
currentSubmesh.triangleCount = triangleCount;
currentSubmesh.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;
}
var flipStatesItems = flipStates.Items;
// Iterate through all slots and store their triangles.
var drawOrderItems = skeleton.DrawOrder.Items;
int triangleIndex = 0; // Modified by loop
for (int i = submeshInstructions.startSlot, n = submeshInstructions.endSlot; i < n; i++) {
Attachment attachment = drawOrderItems[i].attachment;
bool flip = frontFacing && flipStatesItems[i];
// Add RegionAttachment triangles
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;
}
// Add (Weighted)MeshAttachment triangles
int[] attachmentTriangles;
int attachmentVertexCount;
var meshAttachment = attachment as MeshAttachment;
if (meshAttachment != null) {
attachmentVertexCount = meshAttachment.worldVerticesLength >> 1; // length/2
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;
}
}
#endif
///This is a Mesh that also stores the instructions SkeletonRenderer generated for it.
public class SmartMesh {
public Mesh mesh = Spine.Unity.SpineMesh.NewMesh();
public SmartMesh.Instruction instructionUsed = new SmartMesh.Instruction();
public class Instruction {
public bool immutableTriangles;
public int vertexCount = -1;
public readonly ExposedList attachments = new ExposedList();
public readonly ExposedList submeshInstructions = new ExposedList();
#if SPINE_OPTIONAL_FRONTFACING
public bool frontFacing;
public readonly ExposedList attachmentFlips = new ExposedList();
#endif
public void Clear () {
this.attachments.Clear(false);
this.submeshInstructions.Clear(false);
#if SPINE_OPTIONAL_FRONTFACING
this.attachmentFlips.Clear(false);
#endif
}
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);
#if SPINE_OPTIONAL_FRONTFACING
this.frontFacing = other.frontFacing;
this.attachmentFlips.Clear(false);
this.attachmentFlips.GrowIfNeeded(other.attachmentFlips.Capacity);
this.attachmentFlips.Count = other.attachmentFlips.Count;
if (this.frontFacing)
other.attachmentFlips.CopyTo(this.attachmentFlips.Items);
#endif
this.submeshInstructions.Clear(false);
this.submeshInstructions.GrowIfNeeded(other.submeshInstructions.Capacity);
this.submeshInstructions.Count = other.submeshInstructions.Count;
other.submeshInstructions.CopyTo(this.submeshInstructions.Items);
}
}
}
}
}